﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Collections.ObjectModel;
using Spk.Controls.Common;

namespace Spk.Controls.Trees
{
    public partial class VirtualTreeView : UserControl
    {
        // Private constants -------------------------------------------------

        private const uint DefaultNodeHeight = 16;

        private const int WM_THEMECHANGED = 0x031A;

        private const int AUTO_SCROLL_VALUE = 32;
        private const int FAST_SCROLL_DISTANCE = 100;

        private readonly int SCROLLBAR_SMALL_CHANGE = 16;

        // Private structures ------------------------------------------------

        /// <summary>
        /// Determines a way in which user currently interacts with tree
        /// </summary>
        private enum UserInteraction
        {
            Idle,
            RectangularSelection
        }
        
        /// <summary>
        /// Determines autoscroll direction (when drawing rectangular 
        /// selection)
        /// </summary>
        private enum ScrollDirection
        {
            Up,
            Down
        }

        /// <summary>
        /// Determines, which part of node currently has focus
        /// </summary>
        [Flags]
        private enum HotTrackElements
        {
            ExpandMark = 1,
            Checkbox = 2,
            Contents = 4
        }

        /// <summary>
        /// Coordinate system for point.
        /// 
        /// * Absolute - 0, 0 is the left-top point of the whole tree's
        ///              contents. Absolute coordinates does not depend
        ///              on scrolling and drawing.
        /// * Relative - 0, 0 is left-top point of current view area
        ///              (excluding border, scrollbars etc.)
        /// * Client - 0, 0 is left-top point of the control
        /// </summary>
        private enum CoordinateSystem
        {
            /// <summary>Coordinates of tree's contents</summary>
            Absolute, 
            /// <summary>Coordinates of current content view area</summary>
            Relative,
            /// <summary>Coordinates of the control</summary>
            Client
        }

        /// <summary>
        /// Flags determining current state of the TreeView control
        /// </summary>
        [Flags]
        private enum TreeViewStates
        {
            /// <summary>The treeview is just beeing created</summary>
            Creating = 1,
            /// <summary>The treeview is re-evaluating its metrics</summary>
            Measuring = 2,
            /// <summary>The Treeview's metrics are valid</summary>
            Measured = 4,
            /// <summary>The treeview is painting itself</summary>
            Painting = 8,
            /// <summary>Tree is being internally scrolled (without scrollbar being used)</summary>
            ManualScrolling = 16,
            /// <summary>The treeview's position cache is not valid</summary>
            NeedUpdateCache = 32,
            /// <summary>The vertical scrollbar is visible</summary>
            VScrollVisible = 64,
            /// <summary>The horizontal scrollbar is visible</summary>
            HScrollVisible = 128,
            /// <summary>View area is being scrolled</summary>
            Scrolling = 256,
        }

        /// <summary>
        /// Pending events to be raised after the processing is done.
        /// </summary>
        [Flags]
        private enum PendingEvents
        {
            /// <summary>Selection has changed, event shall be raised after finishing processing request</summary>
            SelectionChanged = 1,
            /// <summary>Focused node has changed, event shall be raised after finishing processing request</summary>
            FocusChanged = 2,
            /// <summary>Checked node list has changed, event shall be raised after finishing processing request</summary>
            CheckedNodesChanged = 4
        }

        /// <summary>
        /// Location of the node in regard to the drawing area
        /// </summary>
        private enum NodeRelativeLocation
        {
            /// <summary>Node is above current drawing area</summary>
            Above,
            /// <summary>Node is within current drawing area</summary>
            Inside,
            /// <summary>Node is below current drawing area</summary>
            Below
        }

        /// <summary>
        /// Used to delay call of VisualTreeUpdated
        /// </summary>
        private delegate void InvokeDelegate();

        /// <summary>
        /// Container for data regarding drawing single node on the
        /// screen
        /// </summary>
        /// <remarks>
        /// All rectangles are described in client coordinate system
        /// (ready to draw)
        /// </remarks>
        private class NodePaintInfo
        {
            /// <summary>
            /// General data regarding drawn node
            /// </summary>
            public class GeneralData
            {
                public GeneralData(VirtualNode newNode,
                    string newText,
                    int newImageIndex,
                    bool newShowCheckbox,
                    TreeLineKinds[] newLines)
                {
                    newNode.CheckNull("newNode");

                    Node = newNode;
                    Text = newText;
                    ImageIndex = newImageIndex;
                    ShowCheckbox = newShowCheckbox;
                    Lines = newLines;
                }

                /// <summary>Drawn node</summary>
                public VirtualNode Node { get; private set; }

                /// <summary>Drawn node's text</summary>
                public string Text { get; private set; }

                /// <summary>Image index of drawn node</summary>
                public int ImageIndex { get; private set; }

                /// <summary>Whether to show checkbox for this node or not</summary>
                public bool ShowCheckbox { get; private set; }

                /// <summary>Array of line kinds for drawn node</summary>
                public TreeLineKinds[] Lines { get; private set; }
            }

            /// <summary>
            /// Metrics data allowing one to evaluate apropriate regions
            /// for node's parts
            /// </summary>
            public class MetricsData
            {
                public MetricsData(int newWidth, int newHeight, Rectangle newNodeArea)
                {
                    Width = newWidth;
                    Height = newHeight;
                    NodeArea = newNodeArea;
                }

                /// <summary>Width of the whole node</summary>
                public int Width { get; private set; }

                /// <summary>Height of the whole node</summary>
                public int Height { get; private set; }

                /// <summary>Rectangle the node occupies</summary>
                public Rectangle NodeArea { get; private set; }
            }

            /// <summary>
            /// More detailed data regarding location and size of
            /// node's parts
            /// </summary>
            public class VisualData
            {
                /// <summary>
                /// Detailed data regarding node part's element
                /// locations
                /// </summary>
                public class DetailedData
                {
                    public DetailedData(Rectangle newCheckboxRect,
                        Rectangle newExpandMarkRect,
                        Rectangle newImageRect,
                        Rectangle newTextRect)
                    {
                        CheckboxRect = newCheckboxRect;
                        ExpandMarkRect = newExpandMarkRect;
                        ImageRect = newImageRect;
                        TextRect = newTextRect;
                    }

                    /// <summary>Exact location and size of checkbox</summary>
                    public Rectangle CheckboxRect { get; private set; }

                    /// <summary>Exact location and size of expand glyph</summary>
                    public Rectangle ExpandMarkRect { get; private set; }

                    /// <summary>Exact location and size of image</summary>
                    public Rectangle ImageRect { get; private set; }

                    /// <summary>Exact location and size of text</summary>
                    public Rectangle TextRect { get; private set; }
                }

                public VisualData(Rectangle newCheckboxArea,
                    Rectangle newExpandMarkArea,
                    Rectangle newLineArea,
                    Rectangle newContentArea,
                    DetailedData newDetailed = null)
                {
                    CheckboxArea = newCheckboxArea;
                    ExpandMarkArea = newExpandMarkArea;
                    LineArea = newLineArea;
                    ContentArea = newContentArea;
                    Detailed = newDetailed;
                }

                /// <summary>Area occupied by checbox</summary>
                public Rectangle CheckboxArea { get; private set; }

                /// <summary>Area occupied by expand mark</summary>
                public Rectangle ExpandMarkArea { get; private set; }

                /// <summary>Area occupied by lines</summary>
                public Rectangle LineArea { get; private set; }

                /// <summary>Area occupied by node's content (image and text)</summary>
                public Rectangle ContentArea { get; private set; }

                /// <summary>Detailed data regarding node part's element
                /// locations</summary>
                /// <remarks>This element is optional</remarks>
                public DetailedData Detailed { get; private set; }
            }

            /// <summary>
            /// Container for data, which may change in response to user's
            /// actions
            /// </summary>
            public class DynamicData
            {
                private bool mouseSelected;
                private HotTrackElements hotTrackElements;

                public DynamicData(bool newMouseSelected = false)
                {
                    newMouseSelected = mouseSelected;
                }

                /// <summary>Indicates temporary selection during
                /// rectangular selection process</summary>
                public bool MouseSelected
                {
                    get
                    {
                        return mouseSelected;
                    }
                    set
                    {
                        mouseSelected = value;
                    }
                }

                /// <summary>Lists elements currently under mouse</summary>
                public HotTrackElements HotTrackElements
                {
                    get
                    {
                        return hotTrackElements;
                    }
                    set
                    {
                        hotTrackElements = value;
                    }
                }
            }

            public NodePaintInfo(GeneralData newGeneralData,
                MetricsData newMetricsData,
                VisualData newVisualData,
                DynamicData newDynamicData)
            {
                newGeneralData.CheckNull("newGeneralData");
                newMetricsData.CheckNull("newMetricsData");
                newVisualData.CheckNull("newVisualData");
                newDynamicData.CheckNull("newDynamicData");

                General = newGeneralData;
                Metrics = newMetricsData;
                Visual = newVisualData;
                Dynamic = newDynamicData;
            }

            /// <summary>General data regarding drawn node</summary>
            public GeneralData General { get; private set; }

            /// <summary>Metrics data allowing one to evaluate apropriate regions
            /// for node's parts</summary>
            public MetricsData Metrics { get; private set; }

            /// <summary>More detailed data regarding location and size of
            /// node's parts</summary>
            public VisualData Visual { get; private set; }

            /// <summary>Container for data, which may change in response to user's
            /// actions</summary>
            public DynamicData Dynamic { get; private set; }
        }

        // Private fields ----------------------------------------------------

        // Metrics
        private Rectangle contentArea;
        private Rectangle drawableArea;
        private int maxYOffset;
        private int yOffset;
        private int maxXOffset;
        private int xOffset;

        // Visuals
        private List<NodePaintInfo> visuals;
        private NodePaintInfo hotTrackVisual;
        private ImageList images;
        private NodeRenderer renderer;

        // Internals
        private TreeViewStates states;
        private PendingEvents events;
        private VirtualNode root;
        private List<VirtualNode> selectedNodes;
        private ReadOnlyCollection<VirtualNode> selectedNodesRO;
        private List<VirtualNode> checkedNodes;
        private ReadOnlyCollection<VirtualNode> checkedNodesRO;
        private VirtualNode focusedNode;
        private VirtualNode shiftSelectionOrigin;
        
        private PositionCache positionCache;

        // Interaction
        private UserInteraction userInteraction;
        private Point mouseSelectionStart;
        private Point mouseSelectionEnd;
        private Timer scrollTimer;
        private ScrollDirection scrollDirection;

        // Property backing fields
        private bool allowExpandCollapse;
        private bool showBorder;
        private bool showLines;
        private bool showRootLines;
        private bool showExpandMarks;
        private bool showImages;
        private bool showCheckboxes;
        private bool multiSelect;
        private bool hotTrack;
        private DrawModes drawMode;

        // Internal controls
        private VTVVerticalScrollBar vScrollBar;
        private VTVHorizontalScrollBar hScrollBar;

        // Metrics -----------------------------------------------------------

        private Point ScreenToRelative(Point p)
        {
            return ClientToRelative(PointToClient(p));
        }

        private Point ClientToRelative(Point p)
        {
            Point result = p;
            result.Offset(-drawableArea.Location.X, -drawableArea.Location.Y);

            return result;
        }

        private Point RelativeToClient(Point p)
        {
            Point result = p;
            result.Offset(drawableArea.Location.X, drawableArea.Location.Y);

            return result;
        }

        private Point RelativeToAbsolute(Point relative)
        {
            return new Point(relative.X + xOffset, relative.Y + yOffset);
        }

        private Point AbsoluteToRelative(Point absolute)
        {
            return new Point(absolute.X - xOffset, absolute.Y - yOffset);
        }

        // Private methods ---------------------------------------------------

        /// <summary>
        /// Evaluates, which nodes are intersected by rectangular
        /// selection and applies the actual selection to them.
        /// </summary>
        private void ApplyRectangularSelection()
        {
            Rectangle mouseAbsoluteRect = GetMouseSelectionRect(CoordinateSystem.Absolute);
            
            List<NodePaintInfo> tempVisuals = new List<NodePaintInfo>();
            int maxWidth;
            GenerateVisuals(mouseAbsoluteRect.Top, mouseAbsoluteRect.Bottom, out maxWidth, tempVisuals, CoordinateSystem.Absolute);

            for (int i = 0; i < tempVisuals.Count; i++)
            {
                if (tempVisuals[i].Visual.ContentArea.IntersectsWith(mouseAbsoluteRect) && !tempVisuals[i].General.Node.IsSelected)
                    DoAddToSelection(tempVisuals[i].General.Node);
            }
        }

        /// <summary>
        /// Adds a node to selection
        /// </summary>
        private void DoAddToSelection(VirtualNode node)
        {
            node.CheckNull("node");
            node.CheckOwnerTree(this, "node");

            if (!node.IsSelected)
            {
                node.IsSelected = true;
                selectedNodes.Add(node);

                Invalidate();
                SetPendingEvent(PendingEvents.SelectionChanged);
            }
        }

        /// <summary>
        /// Adds one child to the node
        /// </summary>
        private VirtualNode DoAddChild(VirtualNode node)
        {
            DoSetChildCount(node, node.ChildCount + 1);
            return node.LastChild;
        }

        /// <summary>
        /// Sets node's checked state
        /// </summary>
        private void DoCheckNode(VirtualNode node)
        {
            if (!node.IsChecked)
            {
                checkedNodes.Add(node);
                node.IsChecked = true;
                
                SetPendingEvent(PendingEvents.CheckedNodesChanged);
            }
        }

        /// <summary>
        /// Clears focused node.
        /// </summary>
        /// <remarks>
        /// Note, that the method will immediately clear focused node - even, 
        /// if there are visible nodes (in which case focused node *always*
        /// exist). This method shall be used only internally.
        /// </remarks>
        private void DoClearFocusedNode()
        {
            if (focusedNode != null)
                DoClearFocusedNode(focusedNode);
        }

        /// <summary>
        /// Removes focus from given node (=clears focus)
        /// </summary>
        /// <remarks>
        /// Note, that the method will immediately clear focused node - even, 
        /// if there are visible nodes (in which case focused node *always*
        /// exist). This method shall be used only internally.
        /// </remarks>
        private void DoClearFocusedNode(VirtualNode node)
        {
            node.CheckNull("node");
            node.CheckOwnerTree(this, "node");

            if (node.IsFocused)
            {
                if (node != focusedNode)
                    throw new InvalidOperationException("Internal error: there seems to be more than one focused node!");

                node.IsFocused = false;
                focusedNode = null;

                SetPendingEvent(PendingEvents.FocusChanged);

                Invalidate();
            }
        }

        /// <summary>
        /// Removes temporary mouse selection flag from
        /// currently visible nodes
        /// </summary>
        private void DoClearRectangularSelection()
        {
            for (int i = 0; i < visuals.Count; i++)
                visuals[i].Dynamic.MouseSelected = false;
        }

        /// <summary>
        /// Clears shift selection origin
        /// </summary>
        private void DoClearShiftSelectionOrigin()
        {
            if (shiftSelectionOrigin != null)
                DoClearShiftSelectionOrigin(shiftSelectionOrigin);
        }

        /// <summary>
        /// Removes shift selection origin state from node (=clears shift selection origin)
        /// </summary>
        /// <param name="node"></param>
        private void DoClearShiftSelectionOrigin(VirtualNode node)
        {
            node.CheckNull("node");
            node.CheckOwnerTree(this, "node");

            if (node.IsShiftSelectionOrigin)
            {
                if (node != shiftSelectionOrigin)
                    throw new InvalidOperationException("Internal error: there seems to be more than one shift selection origin node!");

                node.IsShiftSelectionOrigin = false;
                shiftSelectionOrigin = null;
            }
        }

        /// <summary>
        /// Clears the selection
        /// </summary>
        private void DoClearSelection()
        {
            if (selectedNodes.Count > 0)
            {
                for (int i = 0; i < selectedNodes.Count; i++)
                    selectedNodes[i].IsSelected = false;

                selectedNodes.Clear();

                SetPendingEvent(PendingEvents.SelectionChanged);

                Invalidate();
            }
        }

        /// <summary>
        /// Removes hottrack flags from currently visible
        /// nodes
        /// </summary>
        private void DoClearHotTrack()
        {
            if (hotTrackVisual != null)
            {
                hotTrackVisual.General.Node.IsHotTrack = false;
                hotTrackVisual.Dynamic.HotTrackElements = (HotTrackElements)0;
            }

            hotTrackVisual = null;

            Invalidate();
        }

        /// <summary>
        /// Processes mouse click operation over node. Changes selection,
        /// focus and shift selection origin.
        /// </summary>
        private void DoClickNode(VirtualNode hitNode, bool Shift, bool Control)
        {
            if (!multiSelect)
            {
                // Modifier keys does not matter, select and focus item
                DoSetSelection(hitNode);
                DoSetShiftSelectionOrigin(hitNode);
                DoSetFocusedNode(hitNode);
            }
            else
            {
                if (Control && Shift)
                {
                    // Control + Shift + click
                    if (shiftSelectionOrigin != null)
                        DoModifySelectionRange(shiftSelectionOrigin, hitNode, shiftSelectionOrigin.IsSelected);
                    else
                    {
                        DoAddToSelection(hitNode);
                        DoSetShiftSelectionOrigin(hitNode);
                    }
                }
                else if (Shift)
                {
                    // Shift + click

                    if (shiftSelectionOrigin != null)
                        DoModifySelectionRange(shiftSelectionOrigin, hitNode);
                    else
                    {
                        DoSetSelection(hitNode);
                        DoSetShiftSelectionOrigin(hitNode);
                    }
                }
                else if (Control)
                {
                    // Control + click
                    DoSetShiftSelectionOrigin(hitNode);

                    if (!hitNode.IsSelected)
                        DoAddToSelection(hitNode);
                    else
                        DoRemoveFromSelection(hitNode);
                }
                else
                {
                    DoSetSelection(hitNode);
                    DoSetShiftSelectionOrigin(hitNode);
                }

                DoSetFocusedNode(hitNode);
            }

            OnNodeClicked(new NodeEventArgs(hitNode));
        }

        /// <summary>
        /// Collapses the node
        /// </summary>
        private void DoCollapseNode(VirtualNode node)
        {
            node.CheckNull("node");
            node.CheckOwnerTree(this, "node");

            if (node == root)
                throw new InvalidOperationException("Internal error: cannot collapse hidden root node!");

            if (node.IsExpanded)
            {
                OnNodeCollapsing(new NodeEventArgs(node));

                // Removing selection, focus, shift selection origin from children
                InternalRemoveVisuals(node, true, false);

                bool nodeVisible = TreeStructure.IsNodeFullyVisible(node);

                if (nodeVisible)
                    VisualTreeUpdating();

                VerifyNodeUserInitialized(node);

                TreeStructure.CollapseNode(node);

                if (nodeVisible)
                {
                    InvalidateMetrics();
                }

                OnNodeCollapsed(new NodeEventArgs(node));
            }
        }

        /// <summary>
        /// Removes all children from given node
        /// </summary>
        /// <param name="node"></param>
        private void DoDeleteChildren(VirtualNode node, bool ResetHasChildren = true)
        {
            node.CheckNull("node");
            node.CheckOwnerTree(this, "node");

            bool nodeVisible = TreeStructure.IsNodeFullyVisible(node);

            // Resetting HasChildren if requested

            if (ResetHasChildren && node.HasChildren)
            {
                if (nodeVisible)
                {
                    // Node is visible and has children - invalidate is required
                    // to remove the expand mark from screen.
                    // At this point, there may be no need to re-evaluate metrics.
                    Invalidate();
                }

                node.HasChildren = false;
            }

            // If there actually is something to delete...
            if (node.ChildCount > 0)
            {
                // Removing selection, focus, shift selection origin
                InternalRemoveVisuals(node, true, false);

                // If node is expanded, metrics shall be rebuilt.
                if (node.IsExpanded)
                {
                    if (nodeVisible)
                    {
                        // Node is visible and expanded. Metrics have to be
                        // re-evaluated as well as the position cache.

                        InvalidateMetrics();
                        VisualTreeUpdating();
                    }

                    // Node should be collapsed (unless it is hidden root node)
                    if (node != root)
                        node.IsExpanded = false;
                }

                // Removing the children from tree structure
                TreeStructure.DeleteNodeChildren(node);
            }
        }

        /// <summary>
        /// Removes node from the tree. Node is unusable after this
        /// operation.
        /// </summary>
        private void DoDeleteNode(VirtualNode node)
        {
            node.CheckNull("node");
            node.CheckOwnerTree(this, "node");

            if (node == root)
                throw new InvalidOperationException("Internal error: root node cannot be deleted!");

            bool nodeVisible = TreeStructure.IsNodeFullyVisible(node);
            if (nodeVisible)
                VisualTreeUpdating();

            InternalRemoveVisuals(node, true, true);

            TreeStructure.DeleteNode(node);

            // Apply changes to control's states
            if (nodeVisible)
            {
                InvalidateMetrics();
            }
        }

        /// <summary>
        /// Ensures, that passed node is visible to user. This includes
        /// ensuring, that node and its parents are not hidden and that
        /// all of their ancestors are expanded.
        /// </summary>
        private void DoEnsureNodeVisible(VirtualNode node, bool showIfHidden = false)
        {
            node.CheckNull("node");
            node.CheckOwnerTree(this, "node");

            VirtualNode run = node;
            while (run != null)
            {
                if (!run.IsVisible)
                    if (showIfHidden)
                        DoShowNode(run);
                    else
                        throw new InvalidOperationException("Cannot ensure, that node is visible: either the node or one of its parents is invisible. Did you want to use showIfHidden?");

                run = run.Parent;
            }

            run = node.Parent;
            while (run != null)
            {
                if (!run.IsExpanded)
                    DoExpandNode(run);

                run = run.Parent;
            }

            DoScrollToNode(node);
        }

        /// <summary>
        /// Expands the node
        /// </summary>
        private void DoExpandNode(VirtualNode node)
        {
            node.CheckNull("node");
            node.CheckOwnerTree(this, "node");

            if (!node.IsExpanded)
            {
                VerifyNodeUserInitialized(node);
                VerifyChildrenInitialized(node);

                // Node can be expanded only if it has any children.
                uint childCount = node.ChildCount;
                if (childCount > 0)
                {
                    OnNodeExpanding(new NodeEventArgs(node));

                    node.IsExpanded = true;

                    bool nodeVisible = TreeStructure.IsNodeFullyVisible(node);
                    if (nodeVisible)
                        VisualTreeUpdating();

                    // Children are about to be displayed, so they have to be
                    // initiated.

                    VerifyChildrenMeasured(node);

                    uint totalHeight = TreeStructure.EvalTotalHeight(node);

                    DoSetTotalHeight(node, totalHeight, false);

                    if (nodeVisible)
                    {
                        InvalidateMetrics();
                    }

                    OnNodeExpanded(new NodeEventArgs(node));
                }
            }
        }

        /// <summary>
        /// Refreshes informations about mouse rectangular selection (usually
        /// after mouse position or y-offset change)
        /// </summary>
        /// <param name="clientX">X coordinate of mouse in client coordinate system</param>
        /// <param name="clientY">Y coordinate of mouse in client coordinate system</param>
        private void DoHandleRectangularSelection(int clientX, int clientY)
        {
            // Updating mouse rectangle selection
            mouseSelectionEnd = RelativeToAbsolute(ClientToRelative(new Point(clientX, clientY)));
            Rectangle selectionRectangle = GetMouseSelectionRect(CoordinateSystem.Client);

            // Evaluating mouse selection values
            for (int i = 0; i < visuals.Count; i++)
            {

                if (userInteraction == UserInteraction.RectangularSelection)
                {
                    Rectangle itemClip = Rectangle.Intersect(visuals[i].Visual.ContentArea, drawableArea);
                    visuals[i].Dynamic.MouseSelected = (itemClip.IntersectsWith(selectionRectangle));
                }
                else
                    visuals[i].Dynamic.MouseSelected = false;
            }
        }

        /// <summary>
        /// Handles the hottrack for current mouse position.
        /// </summary>
        private void DoHandleHotTrack(int clientX, int clientY)
        {
            if (!hotTrack)
                throw new InvalidOperationException("Internal error: unnecessary evaluation of hottrack, when hot tracking is disabled!");

            int visualIndex = GetVisualIndexAt(new Point(clientX, clientY));

            NodePaintInfo visual;
            if (visualIndex >= 0 && visualIndex < visuals.Count)
                visual = visuals[visualIndex];
            else
                visual = null;

            // Hottracked visual might have changed
            if (hotTrackVisual != visual)
            {
                if (hotTrackVisual != null)
                {
                    // Removing hottrack from current item
                    hotTrackVisual.General.Node.IsHotTrack = false;
                    hotTrackVisual.Dynamic.HotTrackElements = (HotTrackElements)0;
                }

                hotTrackVisual = visual;
                if (hotTrackVisual != null)
                    hotTrackVisual.General.Node.IsHotTrack = true;

                Invalidate();
            }
            
            // Hottracked visual's element might have changed
            if (hotTrackVisual != null)
            {
                HotTrackElements elements = (HotTrackElements)0;
                Point mousePoint = new Point(clientX, clientY);

                if (hotTrackVisual.Visual.ExpandMarkArea.Contains(mousePoint))
                    elements |= HotTrackElements.ExpandMark;
                if (hotTrackVisual.Visual.CheckboxArea.Contains(mousePoint))
                    elements |= HotTrackElements.Checkbox;
                if (hotTrackVisual.Visual.ContentArea.Contains(mousePoint))
                    elements |= HotTrackElements.Contents;

                if (elements != hotTrackVisual.Dynamic.HotTrackElements)
                {
                    hotTrackVisual.Dynamic.HotTrackElements = elements;
                    Invalidate();
                }
            }
        }

        /// <summary>
        /// Hides node (sets its visibility state to false)
        /// </summary>
        private void DoHideNode(VirtualNode node)
        {
            node.CheckNull("node");
            node.CheckOwnerTree(this, "node");

            if (!node.IsVisible)
                return;

            if (TreeStructure.IsNodeVisiblePath(node))
            {
                VisualTreeUpdating();
                InvalidateMetrics();
            }

            InternalRemoveVisuals(node, true, true);

            TreeStructure.HideNode(node);
        }

        /// <summary>
        /// Inserts new node into tree using specific insert method
        /// </summary>
        /// <remarks>
        /// Should be used only by DoInsertNodeAfter and DoInsertNodeBefore
        /// </remarks>
        private VirtualNode DoInsertNode(VirtualNode node, Func<VirtualNode, VirtualNode> insertNode)
        {
            node.CheckNull("node");
            node.CheckOwnerTree(this, "node");

            if (node == root)
                throw new InvalidOperationException("Internal error: cannot insert node after hidden root node!");

            var parent = node.Parent;

            bool nodeVisible = TreeStructure.IsNodeFullyVisible(parent);
            bool needUpdateMetrics = false;

            VirtualNode newNode = insertNode(node);
            newNode.Height = DefaultNodeHeight;
            newNode.TotalHeight = DefaultNodeHeight;

            if (parent.IsExpanded)
            {
                if (nodeVisible)
                    needUpdateMetrics = true;

                DoSetTotalHeight(parent, newNode.Height, true);

                // Initializing and measuring added child, because
                // it is immediately visible
                VerifyNodeUserInitialized(newNode);
                VerifyNodeMeasured(newNode);
            }

            if (needUpdateMetrics)
            {
                VisualTreeUpdating();
                InvalidateMetrics();
            }

            return newNode;
        }

        /// <summary>
        /// Inserts new node after the chosen one
        /// </summary>
        private VirtualNode DoInsertNodeAfter(VirtualNode node)
        {
            return DoInsertNode(node, (VirtualNode relNode) => TreeStructure.InsertChildAfter(relNode)); 
        }

        /// <summary>
        /// Inserts new node before the chosen one
        /// </summary>
        private VirtualNode DoInsertNodeBefore(VirtualNode node)
        {
            return DoInsertNode(node, (VirtualNode relNode) => TreeStructure.InsertChildBefore(relNode));
        }

        /// <summary>
        /// Selects or deselects range of nodes.
        /// </summary>
        /// <param name="first">First node of range.</param>
        /// <param name="second">Last node of range.</param>
        /// <param name="select">Controls, whether to select or deselect nodes</param>
        private void DoModifySelectionRange(VirtualNode first, VirtualNode second, bool select = true)
        {
            first.CheckNull("first");
            second.CheckNull("second");
            first.CheckOwnerTree(this, "first");
            second.CheckOwnerTree(this, "second");

            if (!TreeStructure.IsNodeFullyVisible(first) || !TreeStructure.IsNodeFullyVisible(second))
                throw new InvalidOperationException("Internal error: attempting to add range to selection, while one of nodes is not immediately visible!");

            VirtualNode from, to;

            switch (TreeStructure.Compare(first, second))
            {
                case NodeVisualRelationship.Earlier:
                    {
                        from = first;
                        to = second;

                        break;
                    }
                case NodeVisualRelationship.Later:
                    {
                        from = second;
                        to = first;

                        break;
                    }
                case NodeVisualRelationship.Equal:
                    {
                        from = second;
                        to = second;

                        break;
                    }
                default:
                    throw new InvalidOperationException("Unsupported node comparison result!");
            }

            VirtualNode node = from;
            bool finish = false;
            do
            {
                if (node == to)
                    finish = true;
                if (select)
                    DoAddToSelection(node);
                else
                    DoRemoveFromSelection(node);

                node = TreeStructure.GetNextVisible(node);
            }
            while (!finish);
        }

        /// <summary>
        /// Forces every measured node to be measured again. Method
        /// is called, when a change is introduced, which influences
        /// metrics of elements.
        /// </summary>
        /// <remarks>Very slow. Should not be called after populating 
        /// the tree.</remarks>
        private void DoRemeasure(VirtualNode node)
        {
            if (node == root)
                node.Height = 0;
            else
            {
                // Remeasure node only if it had already been measured before.
                if (node.IsMeasured)
                    InternalMeasureNode(node);
            }

            for (VirtualNode child = node.FirstChild; child != null; child = child.NextSibling)
                DoRemeasure(child);

            node.TotalHeight = TreeStructure.EvalTotalHeight(node);
        }

        /// <summary>
        /// Removes node from selection
        /// </summary>
        private void DoRemoveFromSelection(VirtualNode node)
        {
            node.CheckNull("node");
            node.CheckOwnerTree(this, "node");

            if (node.IsSelected)
            {
                int index = selectedNodes.IndexOf(node);
                if (index == -1)
                    throw new InvalidOperationException("Internal error: selected node is not on internal list!");

                selectedNodes.RemoveAt(index);

                node.IsSelected = false;

                SetPendingEvent(PendingEvents.SelectionChanged);

                Invalidate();
            }
        }

        /// <summary>
        /// Removes node from selection
        /// </summary>
        /// <param name="selectedNodeIndex">Index of node on selection list</param>
        private void DoRemoveFromSelection(int selectedNodeIndex)
        {
            if (selectedNodeIndex < 0 || selectedNodeIndex >= selectedNodes.Count)
                throw new ArgumentOutOfRangeException("selectedNodeIndex");

            VirtualNode node = selectedNodes[selectedNodeIndex];
            if (node.IsSelected)
            {
                selectedNodes.RemoveAt(selectedNodeIndex);
                node.IsSelected = false;

                SetPendingEvent(PendingEvents.SelectionChanged);

                Invalidate();
            }
        }

        /// <summary>
        /// Removes node and its children from selection
        /// </summary>
        /// <param name="node"></param>
        private void DoRemoveFromSelectionRecursive(VirtualNode node)
        {
            if (node.IsSelected)
                DoRemoveFromSelection(node);

            int i = 0;
            while (i < selectedNodes.Count)
            {
                if (TreeStructure.IsAncestorOf(node, selectedNodes[i]))
                    DoRemoveFromSelection(selectedNodes[i]);
                else
                    i++;
            }
        }

        /// <summary>
        /// Sets child count for node and generates (or removes)
        /// children if needed.
        /// </summary>
        private void DoSetChildCount(VirtualNode node, uint value)
        {
            node.CheckNull("node");
            node.CheckOwnerTree(this, "node");

            if (value == 0)
            {
                // SetChildCount(node, 0) means deleting all node's children.
                // DoDeleteChildren has better performance than SetChildCount,
                // so it is called instead. Also, it takes responsibility of
                // performing required invalidate(s).
                DoDeleteChildren(node, true);
            }
            else if (node.ChildCount < value)
            {
                // Adding children

                bool nodeVisible = TreeStructure.IsNodeFullyVisible(node);
                bool needUpdateMetrics = false;
                bool needInvalidate = false;

                // Adding new subnodes
                uint originalCount = node.ChildCount;
                uint newHeight = 0;

                while (node.ChildCount < value)
                {
                    // Creating new node
                    VirtualNode newNode = TreeStructure.AppendChild(node);
                    newNode.Height = DefaultNodeHeight;
                    newNode.TotalHeight = DefaultNodeHeight;

                    // Storing some local data
                    newHeight += newNode.Height;
                }

                // Updating node's states and internals

                if (!node.HasChildren)
                {
                    if (nodeVisible)
                        needInvalidate = true;

                    node.HasChildren = true;
                }

                if (node.IsExpanded)
                {
                    if (nodeVisible)
                        needUpdateMetrics = true;

                    DoSetTotalHeight(node, newHeight, true);

                    // Initializing and measuring children, because
                    // they are immediately visible
                    var childNode = node.LastChild;
                    while (childNode != null && childNode.Index >= originalCount)
                    {
                        VerifyNodeUserInitialized(childNode);
                        VerifyNodeMeasured(childNode);

                        childNode = childNode.PreviousSibling;
                    }
                }

                if (focusedNode == null && root.FirstChild != null)
                    DoSetFocusedNode(root.FirstChild);

                if (needUpdateMetrics)
                {
                    VisualTreeUpdating();
                    InvalidateMetrics();
                }
                else if (needInvalidate)
                {
                    Invalidate();
                }
            }
            else if (node.ChildCount > value)
            {
                // Removing children. DoDeleteNode will handle
                // changes of visuals.
                while (node.ChildCount > value)
                    DoDeleteNode(node.LastChild);
            }
        }

        /// <summary>
        /// Set given node as focused
        /// </summary>
        private void DoSetFocusedNode(VirtualNode node)
        {
            if (node != null)
            {
                node.CheckOwnerTree(this, "node");

                if (!TreeStructure.IsNodeFullyVisible(node))
                    throw new InvalidOperationException("Internal error: attempt to set focus to invisible node!");

                if (focusedNode != node)
                {
                    DoClearFocusedNode();

                    node.IsFocused = true;
                    focusedNode = node;

                    SetPendingEvent(PendingEvents.FocusChanged);

                    Invalidate();
                }
            }
            else
                DoClearFocusedNode();
        }

        /// <summary>
        /// Sets node's checked state
        /// </summary>
        private void DoSetNodeChecked(VirtualNode node, bool @checked)
        {
            if (node.IsChecked != @checked)
            {
                if (@checked)
                    DoCheckNode(node);
                else
                    DoUncheckNode(node);
            }
        }

        /// <summary>
        /// Sets height of node
        /// </summary>
        private void DoSetNodeHeight(VirtualNode node, uint value)
        {
            node.CheckNull("node");
            node.CheckOwnerTree(this, "node");

            if (value == 0)
                throw new ArgumentException("Node height has to be greater than zero!");

            if (TreeStructure.SetNodeHeight(node, value))
            {
                VisualTreeUpdating();
                InvalidateMetrics();
            }
        }

        /// <summary>
        /// Sets selection to single node
        /// </summary>
        private void DoSetSelection(VirtualNode node)
        {
            if (node != null)
                node.CheckOwnerTree(this, "node");

            DoClearSelection();
            if (node != null)
                DoAddToSelection(node);
        }

        /// <summary>
        /// Sets shift selection origin to given node
        /// </summary>
        private void DoSetShiftSelectionOrigin(VirtualNode node)
        {
            if (node != null)
            {
                node.CheckOwnerTree(this, "node");

                if (!TreeStructure.IsNodeFullyVisible(node))
                    throw new InvalidOperationException("Internal error: attempt to set shift selection origin to invisible node!");

                if (shiftSelectionOrigin != node)
                {
                    DoClearShiftSelectionOrigin();

                    node.IsShiftSelectionOrigin = true;
                    shiftSelectionOrigin = node;
                }
            }
        }

        /// <summary>
        /// Modifies total height for the node and all its parents
        /// </summary>
        /// <param name="node">Node, which total height is to be modified</param>
        /// <param name="newHeight">Value of new total height</param>
        /// <param name="relative">Indicates, if the value of newHeight is absolute or relative.</param>
        private void DoSetTotalHeight(VirtualNode node, long newHeight, bool relative = false)
        {
            node.CheckNull("node");
            node.CheckOwnerTree(this, "node");

            if (TreeStructure.SetTotalNodeHeight(node, newHeight, relative))
            {
                VisualTreeUpdating();
                InvalidateMetrics();
            }
        }

        /// <summary>
        /// Scrolls the tree, such that given node is visible.
        /// </summary>
        /// <remarks>
        /// Node has to be visible.
        /// </remarks>
        private void DoScrollToNode(VirtualNode node)
        {
            node.CheckNull("node");
            node.CheckOwnerTree(this, "node");

            if (!TreeStructure.IsNodeFullyVisible(node))
                throw new InvalidOperationException("Internal error: Cannot scroll to invisible node!");

            int nodeYOffset;
            nodeYOffset = EvalNodeYOffset(node);

            // Deciding, whether there's a need for scrolling at all.
            // The node is considered to be visible if:
            // * it is fully visible if height of control allows that
            // * is aligned to top edge of drawing area, otherwise.

            NodeRelativeLocation nodeLocation = GetNodeRelativeLocation(node.Height, nodeYOffset);

            if (nodeLocation != NodeRelativeLocation.Inside)
            {
                int newYOffset;

                if (node.Height >= drawableArea.Height || nodeLocation == NodeRelativeLocation.Above)
                    newYOffset = nodeYOffset;
                else
                    newYOffset = nodeYOffset - drawableArea.Height + (int)node.Height;

                if (newYOffset < 0)
                    newYOffset = 0;
                else if (newYOffset > maxYOffset)
                    newYOffset = maxYOffset;

                ScrollTo(newYOffset);
            }
        }

        /// <summary>
        /// Shows node (sets its visibility state to true)
        /// </summary>
        private void DoShowNode(VirtualNode node)
        {
            node.CheckNull("node");
            node.CheckOwnerTree(this, "node");

            if (node.IsVisible)
                return;

            bool nodeInVisualPath = TreeStructure.IsNodeVisiblePath(node);
            if (nodeInVisualPath)
                VisualTreeUpdating();

            TreeStructure.ShowNode(node);

            if (nodeInVisualPath)
            {
                InvalidateMetrics();
            }
        }

        /// <summary>
        /// Changes the expanded state of the node
        /// </summary>
        private void DoToggleNode(VirtualNode node)
        {
            node.CheckNull("node");
            node.CheckOwnerTree(this, "node");

            if (node.HasChildren)
            {
                if (!node.IsExpanded)
                    DoExpandNode(node);
                else
                    DoCollapseNode(node);
            }
        }

        /// <summary>
        /// Unchecks node
        /// </summary>
        private void DoUncheckNode(VirtualNode node)
        {
            if (node.IsChecked)
            {
                checkedNodes.Remove(node);
                node.IsChecked = false;
                
                SetPendingEvent(PendingEvents.CheckedNodesChanged);
            }
        }

        /// <summary>
        /// Evaluates visual y-position of node.
        /// </summary>
        private int EvalNodeYOffset(VirtualNode node)
        {
            node.CheckNull("node");
            node.CheckOwnerTree(this, "node");

            int nodeYOffset;
            if (!positionCache.TryGetNodeOffset(node, out nodeYOffset))
            {
                VirtualNode run = TreeStructure.GetFirstVisible(root);
                nodeYOffset = 0;

                while (run != null && run != node)
                {
                    nodeYOffset += (int)run.Height;
                    run = TreeStructure.GetNextVisible(run);
                }

                if (run != node)
                    throw new InvalidOperationException("Internal error: corrupted tree structure!");
            }

            return nodeYOffset;
        }

        /// <summary>
        /// Generate an internal list of nodes to be currently displayed.
        /// </summary>
        /// <param name="startOffset">The absolute offset to start the search for</param>
        /// <param name="endOffset">The absolute offset the processing should stop at (excluding).</param>
        /// <param name="maxWidth">Maximal width of node evaluated during the process</param>
        /// <param name="visuals">List, the results will be stored in</param>
        /// <param name="relativeCoords">Whether to use absolute or relative coordinates of NodePaintInfo</param>
        private void GenerateVisuals(int startOffset, int endOffset, out int maxWidth, List<NodePaintInfo> visuals, CoordinateSystem coords = CoordinateSystem.Client)
        {
            visuals.CheckNull("visuals");

            if (startOffset > endOffset)
                throw new ArgumentException("Internal error: endOffset is smaller than startOffset!");

            VirtualNode current;
            int absolutePosition;

            // Finding first node to process
            if (startOffset < 0)
            {
                // Requested absolute position is smaller than zero
                // but possibly some nodes may be visible anyway.
                // This is a "scrolled before data" situation.

                current = TreeStructure.GetFirstVisible(root);
                absolutePosition = 0;
            }
            else if (startOffset >= root.TotalHeight)
            {
                // Requested absolute position is larger than whole
                // tree's height. No nodes will be displayed.

                current = null;
                absolutePosition = 0;
            }
            else
            {
                // Requested absolute position is within the 
                // tree's height.

                GetNodeAt(startOffset, out current, out absolutePosition, false);
            }

            maxWidth = 0;

            // Rebuilding visuals
            visuals.Clear();

            while (current != null && absolutePosition < endOffset)
            {
                bool showCheckbox = false;
                if (showCheckboxes)
                {
                    GetNodeCheckboxVisibilityEventArgs args = new GetNodeCheckboxVisibilityEventArgs(current);
                    OnShowNodeCheckbox(args);
                    showCheckbox = args.ShowCheckbox;
                }

                // General data about the node
                NodePaintInfo.GeneralData general = new NodePaintInfo.GeneralData(current,
                    GetNodeText(current),
                    GetNodeImageIndex(current),
                    showCheckbox,
                    TreeStructure.GetLinesFor(current, showRootLines));

                // Whole node metrics (size, placement onscreen)
                int width = (int)renderer.EvalNodeWidth(general);
                if (width > maxWidth)
                    maxWidth = width;

                int x, y;
                switch (coords)
                {
                    case CoordinateSystem.Client:
                        {
                            x = drawableArea.Left - xOffset;
                            y = absolutePosition - yOffset + drawableArea.Top;
                            break;
                        }
                    case CoordinateSystem.Absolute:
                        {
                            x = 0;
                            y = absolutePosition;
                            break;
                        }
                    case CoordinateSystem.Relative:
                        {
                            x = -xOffset;
                            y = absolutePosition - yOffset;
                            break;
                        }
                    default:
                        throw new ArgumentException("Unsupported coordinate system!");
                }

                Rectangle nodeArea = new Rectangle(x, y, width, (int)current.Height);

                NodePaintInfo.MetricsData metrics = new NodePaintInfo.MetricsData(width, (int)current.Height, nodeArea);

                // Detailed information about node metrics
                // TODO: This is not really required here and can be moved outside the loop.
                NodePaintInfo.VisualData visual = renderer.EvalVisual(general, metrics);
                NodePaintInfo.DynamicData dynamic = new NodePaintInfo.DynamicData();

                NodePaintInfo paintInfo = new NodePaintInfo(general, metrics, visual, dynamic);

                // Add to visuals
                visuals.Add(paintInfo);

                // Moving to next node
                absolutePosition += (int)current.Height;
                current = TreeStructure.GetNextVisible(current);
            }
        }

        /// <summary>
        /// Measures the control.
        /// </summary>
        private void EvalMetrics()
        {
            if ((states & TreeViewStates.Measured) == 0)
            {
                Point local = PointToClient(Cursor.Position);

                try
                {
                    states |= TreeViewStates.Measuring;

                    if ((states & TreeViewStates.Scrolling) != 0)
                    {
                        // User is scrolling the view. Scrollbars are locked.
                        int maxWidth;
                        GenerateVisuals(yOffset, yOffset + contentArea.Height, out maxWidth, visuals);

                        // Evaluate rectangular selection (if needed)
                        if (userInteraction == UserInteraction.RectangularSelection)
                            DoHandleRectangularSelection(local.X, local.Y);
                    }
                    else
                    {
                        contentArea = this.ClientRectangle;
                        if (showBorder)
                            contentArea.Inflate(-1, -1);

                        int maxWidth = 0;
                        bool vScrollBarVisible = false, hScrollBarVisible = false;

                        // The loop usually will execute only once. The exceptions include
                        // situations when xOffset's or yOffset's values become invalid 
                        // due to changes of size of drawableArea during processing.

                        bool finished = false;
                        while (!finished)
                        {
                            drawableArea = contentArea;

                            // Evaluating visuals

                            // At least one vertical screen (= contentArea) has to be included
                            // to avoid problems with scrolling to the bottom of the tree
                            int maxSeekOffset = Math.Max(0, (int)(root.TotalHeight - contentArea.Height));

                            // Evaluating start and end offsets for generating visuals
                            int startOffset = Math.Min(yOffset, maxSeekOffset);
                            int endOffset = startOffset + contentArea.Height;

                            GenerateVisuals(startOffset, endOffset, out maxWidth, visuals); 

                            // Visuals may not be final, so evaluating dynamics here is not
                            // necessary

                            // Evaluating, if scrollbars are needed
                            if (root.TotalHeight > drawableArea.Height)
                            {
                                vScrollBarVisible = true;
                                drawableArea.Width -= vScrollBar.Width;
                            }

                            if (maxWidth > drawableArea.Width)
                            {
                                hScrollBarVisible = true;
                                drawableArea.Height -= hScrollBar.Height;
                            }

                            // Another check of vertical scrollbar is required if horizontal
                            // scrollbar is visible, because it decreased the drawable area's
                            // height - possibly below the total height of tree's items.
                            if (hScrollBarVisible && !vScrollBarVisible && root.TotalHeight > drawableArea.Height)
                            {
                                vScrollBarVisible = true;
                                drawableArea.Width -= vScrollBar.Width;
                            }

                            // Evaluating maximal offsets
                            maxYOffset = (Math.Max(0, (int)root.TotalHeight - drawableArea.Height));
                            maxXOffset = (Math.Max(0, (int)maxWidth - drawableArea.Width));

                            if (xOffset > maxXOffset)
                            {
                                xOffset = maxXOffset;

                                // If xOffset changes, visuals have to be generated again
                                continue;
                            }

                            if (yOffset > maxYOffset)
                            {
                                yOffset = maxYOffset;

                                // If yOffset changes, visibility of scrollbars may change
                                // and visuals have to be generated again
                                continue;
                            }

                            finished = true;
                        }

                        // Evaluate rectangular selection (if needed)
                        if (userInteraction == UserInteraction.RectangularSelection)
                            DoHandleRectangularSelection(local.X, local.Y);

                        SetupVertivalScrollbar(vScrollBarVisible, hScrollBarVisible);
                        SetupHorizontalScrollbar(vScrollBarVisible, hScrollBarVisible);
                    }

                    states |= TreeViewStates.Measured;
                }
                finally
                {
                    states &= ~TreeViewStates.Measuring;
                }

                // Evaluate hottrack
                // Important: Hottrack evaluation has to be done, when
                // everything is measured - otherwise we'll get stack
                // overflow (infinite recursion)
                if (hotTrack)
                    DoHandleHotTrack(local.X, local.Y);
            }
        }

        /// <summary>
        /// Returns mouse selection rectangle in specified coordinate system
        /// </summary>
        /// <param name="coordinateSystem">Required coordinate system</param>
        private Rectangle GetMouseSelectionRect(CoordinateSystem coordinateSystem)
        {
            switch (coordinateSystem)
            {
                case CoordinateSystem.Absolute:
                    {
                        Point location = new Point(Math.Min(mouseSelectionStart.X, mouseSelectionEnd.X), Math.Min(mouseSelectionStart.Y, mouseSelectionEnd.Y));
                        Size size = new Size(Math.Abs(mouseSelectionEnd.X - mouseSelectionStart.X + 1), Math.Abs(mouseSelectionEnd.Y - mouseSelectionStart.Y + 1));
                        return new Rectangle(location, size);
                    }
                case CoordinateSystem.Relative:
                    {
                        var relMouseStart = AbsoluteToRelative(mouseSelectionStart);
                        var relMouseEnd = AbsoluteToRelative(mouseSelectionEnd);

                        Point location = new Point(Math.Min(relMouseStart.X, relMouseEnd.X), Math.Min(relMouseStart.Y, relMouseEnd.Y));
                        Size size = new Size(Math.Abs(relMouseEnd.X - relMouseStart.X + 1), Math.Abs(relMouseEnd.Y - relMouseStart.Y + 1));
                        return new Rectangle(location, size);
                    }
                case CoordinateSystem.Client:
                    {
                        var clMouseStart = RelativeToClient(AbsoluteToRelative(mouseSelectionStart));
                        var clMouseEnd = RelativeToClient(AbsoluteToRelative(mouseSelectionEnd));

                        Point location = new Point(Math.Min(clMouseStart.X, clMouseEnd.X), Math.Min(clMouseStart.Y, clMouseEnd.Y));
                        Size size = new Size(Math.Abs(clMouseEnd.X - clMouseStart.X + 1), Math.Abs(clMouseEnd.Y - clMouseStart.Y + 1));
                        return new Rectangle(location, size);
                    }
                default:
                    throw new ArgumentException("Unsupported coordinate system!");
            }
        }

        /// <summary>
        /// Gets first fully visible node in the currently visible area. If
        /// none can be found, returns first with visible top edge.
        /// If again none can be found, returns node occupying
        /// defined area.
        /// </summary>
        private VirtualNode GetFirstFullyVisible()
        {
            return GetFirstFullyVisible(yOffset, drawableArea.Height);
        }

        /// <summary>
        /// Gets first fully visible node in the defined area. If
        /// none can be found, returns first with visible top edge.
        /// If again none can be found, returns node occupying
        /// defined area.
        /// </summary>
        /// <param name="definedYOffset">Location of top edge of the area</param>
        /// <param name="definedAreaHeight">Height of the area</param>
        /// <returns></returns>
        private VirtualNode GetFirstFullyVisible(int definedYOffset, int definedAreaHeight)
        {
            VirtualNode node;
            int position;
            GetNodeAt(definedYOffset, out node, out position, false);

            if (node != null && position < definedYOffset)
            {
                if (position + node.Height - 1 <= definedYOffset + definedAreaHeight - 1)
                {
                    VirtualNode tmpNode = TreeStructure.GetNextVisible(node);
                    if (tmpNode != null)
                        node = tmpNode;
                }
            }

            return node;
        }

        /// <summary>
        /// Gets last fully visible node in the currently visible area. If
        /// none can be found, returns last with visible bottom edge.
        /// If again none can be found, returns node occupying defined
        /// area.
        /// </summary>
        private VirtualNode GetLastFullyVisible()
        {
            return GetLastFullyVisible(yOffset, drawableArea.Height);
        }

        /// <summary>
        /// Gets last fully visible node in the defined area. If
        /// none can be found, returns last with visible bottom edge.
        /// If again none can be found, returns node occupying defined
        /// area.
        /// </summary>
        /// <param name="definedYOffset">Location of top edge of the area</param>
        /// <param name="definedAreaHeight">Height of the area</param>
        /// <returns></returns>
        private VirtualNode GetLastFullyVisible(int definedYOffset, int definedAreaHeight)
        {
            if (visuals.Count == 0)
                return null;
            else
            {
                VirtualNode node;
                int position;
                GetNodeAt(definedYOffset, out node, out position, false);

                if (node != null)
                {
                    VirtualNode lastNode = node;
                    VirtualNode nextNode = TreeStructure.GetNextVisible(node);
                    int nextPosition = position + (int)lastNode.Height;

                    while (nextNode != null && nextPosition + nextNode.Height <= definedYOffset + definedAreaHeight)
                    {
                        nextPosition += (int)nextNode.Height;
                        lastNode = nextNode;
                        nextNode = TreeStructure.GetNextVisible(lastNode);
                    }

                    node = lastNode;
                }

                return node;
            }
        }

        /// <summary>
        /// Finds node, which occupies specified space in the tree.
        /// </summary>
        /// <param name="x">X-coordinate of analyzed point. Effectively ignored.</param>
        /// <param name="y">Y-coordinate of analyzed point.</param>
        /// <param name="node">Found node</param>
        /// <param name="position">Y-position of found node</param>
        /// <param name="relative">Regulates, whether to treat <paramref name="x"/>, <paramref name="y"/> 
        /// and <paramref name="position"/> as relative or absolute positions</param>
        private void GetNodeAt(int y, out VirtualNode node, out int position, bool relative = true)
        {
            int absolutePos = y;
            if (relative)
                absolutePos += yOffset;

            // If requested position is outside tree, return nothing
            if (absolutePos < 0 || absolutePos >= root.TotalHeight)
            {
                node = null;
                position = 0;
            }

            int currentPos;
            VirtualNode result;

            if (!positionCache.TryFindNearestEntry(absolutePos, out result, out currentPos))
            {
                result = TreeStructure.GetFirstVisible(root);
                currentPos = 0;
            }

            while (result != null && result != root && absolutePos > (currentPos + result.Height - 1))
            {
                if (absolutePos > currentPos + result.TotalHeight - 1)
                {
                    currentPos += (int)result.TotalHeight;
                    if (result.NextSibling != null)
                        result = TreeStructure.GetNextVisible(result, true); 
                    else
                        result = TreeStructure.GetNextVisible(result);
                }
                else
                {
                    currentPos += (int)result.Height;
                    result = TreeStructure.GetNextVisible(result);
                }
            }

            if (result == root)
                result = null;

            if (result != null)
            {
                position = currentPos;
                node = result;
                if (relative)
                    position -= yOffset;
            }
            else
            {
                node = null;
                position = 0;
            }
        }

        /// <summary>
        /// Gets node's position in relation to the visible area
        /// </summary>
        private NodeRelativeLocation GetNodeRelativeLocation(VirtualNode node)
        {
            node.CheckNull("node");
            node.CheckOwnerTree(this, "node");

            if (!TreeStructure.IsNodeFullyVisible(node))
                throw new InvalidOperationException("Internal error: cannot evaluate relative location of invisible node!");

            int nodeYOffset = EvalNodeYOffset(node);

            return GetNodeRelativeLocation(node.Height, nodeYOffset);
        }

        /// <summary>
        /// Gets node's position in relation to the visible area
        /// </summary>
        private NodeRelativeLocation GetNodeRelativeLocation(uint nodeHeight, int nodeYOffset)
        {
            if (nodeYOffset < yOffset)
                return NodeRelativeLocation.Above;
            else if (nodeYOffset - yOffset + nodeHeight > drawableArea.Height)
                return NodeRelativeLocation.Below;
            else
                return NodeRelativeLocation.Inside;
        }

        /// <summary>
        /// Gets the text to be displayed for node. If there's a handler
        /// for GetText event, user is asked for the text. Otherwise,
        /// default text is generated.
        /// </summary>
        private string GetNodeText(VirtualNode node)
        {
            node.CheckNull("node");
            node.CheckOwnerTree(this, "node");

            if (node == root)
                throw new InvalidOperationException("Internal error: cannot get text for hidden root node!");

            if (GetText != null)
            {
                GetTextEventArgs args = new GetTextEventArgs(node);
                OnGetText(args);
                return args.Text;
            }
            else
                return node.ToString();
        }

        /// <summary>
        /// Gets the image index to be displayed for node. If there's a
        /// handler for GetImageIndex event, user is asked for image
        /// index. Otherwise, no image is chosen (-1).
        /// </summary>
        private int GetNodeImageIndex(VirtualNode node)
        {
            node.CheckNull("node");
            node.CheckOwnerTree(this, "node");

            if (node == root)
                throw new InvalidOperationException("Internal error: cannot get image index for hidden root node!");

            if (GetImageIndex != null)
            {
                GetImageIndexEventArgs args = new GetImageIndexEventArgs(node);
                OnGetImageIndex(args);
                return args.ImageIndex;
            }
            else
                return -1;
        }

        /// <summary>
        /// Returns index of visual element occupying given point.
        /// </summary>
        /// <returns>Index of element in <seealso cref="visuals"/> field</returns>
        private int GetVisualIndexAt(Point clientPoint)
        {
            VerifyMetrics();

            for (int i = 0; i < visuals.Count; i++)
            {
                if (visuals[i].Metrics.NodeArea.Contains(clientPoint))
                    return i;
            }

            return -1;
        }

        private VirtualNode InternalFindNodeByData(VirtualNode root, object data)
        {
            if (root.Data == data)
                return root;

            var child = root.FirstChild;
            while (child != null)
            {
                if (child.Data == data)
                    return child;

                var result = InternalFindNodeByData(child, data);
                if (result != null)
                    return result;

                child = child.NextSibling;
            }

            return null;
        }

        /// <summary>
        /// Initiates the node
        /// </summary>
        /// <remarks>Method does not set any tree flags.</remarks>
        private void InternalInitNode(VirtualNode node)
        {
            if (InitNode != null)
            {
                var initialArgs = new InitNodeEventArgs(node);
                OnGetNodeInitialData(initialArgs);

                node.IsVisible = initialArgs.Visible;
                node.HasChildren = initialArgs.HasChildren;
                node.Data = initialArgs.Data;

                if (initialArgs.Checked)
                    DoCheckNode(node);
            }
            else
            {
                node.IsVisible = true;
                node.HasChildren = false;
            }

            node.IsUserInitiated = true;
        }

        /// <summary>
        /// Measures the node
        /// </summary>
        /// <remarks>Method does not set any tree flags.</remarks>
        private void InternalMeasureNode(VirtualNode node)
        {
            DoSetNodeHeight(node, renderer.EvalNodeHeight(node));
            node.IsMeasured = true;
        }

        /// <summary>
        /// Removes shift select origin from node's children
        /// if one of them has one.
        /// </summary>
        /// <param name="node">Root node for search</param>
        /// <param name="includingNode">Whether to include root node
        /// while removing shift selection origin</param>
        private void InternalRemoveShiftSelect(VirtualNode node, bool includingNode)
        {
            if (shiftSelectionOrigin != null && TreeStructure.IsAncestorOf(node, shiftSelectionOrigin, includingNode))
                DoClearShiftSelectionOrigin();
        }

        /// <summary>
        /// Removes focus from node's children if one of them
        /// has one.
        /// </summary>
        /// <param name="node">Root node for search</param>
        /// <param name="includingNode">Whether to include root node
        /// while removing focus</param>
        private void InternalRemoveFocus(VirtualNode node, bool includingNode)
        {
            if (!includingNode && TreeStructure.IsAncestorOf(node, focusedNode))
            {
                if (node == root)
                    DoClearFocusedNode();
                else
                    DoSetFocusedNode(node);
            }
            else if (includingNode && TreeStructure.IsAncestorOf(node, focusedNode, true))
            {
                if (node.NextSibling != null)
                    DoSetFocusedNode(node.NextSibling);
                else if (node.PreviousSibling != null)
                    DoSetFocusedNode(node.PreviousSibling);
                else if (node.Parent != null && node.Parent != root)
                    DoSetFocusedNode(node.Parent);
                else
                    DoClearFocusedNode();
            }
        }

        /// <summary>
        /// Unchecks node's children
        /// </summary>
        /// <param name="node">Root node for search</param>
        /// <param name="includingNode">Whether to uncheck root node as well</param>
        private void InternalRemoveCheck(VirtualNode node, bool includingNode)
        {
            if (checkedNodes.Count > 0)
                for (int i = checkedNodes.Count - 1; i >= 0; i--)
                {
                    if (TreeStructure.IsAncestorOf(node, checkedNodes[i]))
                        DoUncheckNode(checkedNodes[i]);
                }

            if (includingNode && node.IsChecked)
                DoUncheckNode(node);
        }

        /// <summary>
        /// Removes selection from node's children if any of them
        /// is selected
        /// </summary>
        /// <param name="node">Root node for search</param>
        /// <param name="includingNode">Whether to unselect root node as well</param>
        private void InternalRemoveSelection(VirtualNode node, bool includingNode)
        {
            if (selectedNodes.Count > 0)
                for (int i = selectedNodes.Count - 1; i >= 0; i--)
                {
                    if (TreeStructure.IsAncestorOf(node, selectedNodes[i]))
                        DoRemoveFromSelection(i);
                }

            if (includingNode && node.IsSelected)
                DoRemoveFromSelection(node);
        }

        /// <summary>
        /// Clears selection, focus and shift selection from node's children
        /// (and, optionally, node itself). The method should be used before
        /// the passed node is deleted or hidden.
        /// </summary>
        /// <param name="node">Root node of subtree to be affected</param>
        /// <param name="includingNode">Whether to remove visuals from the node itself or not</param>
        /// <param name="selectionOnly">Whether to keep check state of nodes or not</param>
        private void InternalRemoveVisuals(VirtualNode node, bool selectionOnly, bool includingNode)
        {
            node.CheckNull("node");
            node.CheckOwnerTree(this, "node");

            if (node == root && includingNode)
                throw new InvalidOperationException("Internal error: cannot perform InternalRemoveVisuals on root node! (includingNode should be false?)");

            // Selection

            InternalRemoveSelection(node, includingNode);

            // Check

            if (!selectionOnly)
                InternalRemoveCheck(node, includingNode);

            // Focus

            InternalRemoveFocus(node, includingNode);

            // ShiftSelect            

            InternalRemoveShiftSelect(node, includingNode);
        }

        /// <summary>
        /// Clears the Measured and VisualsMeasured tree flags.
        /// </summary>
        private void InvalidateMetrics()
        {
            states &= ~(TreeViewStates.Measured);
            Invalidate();
        }

        /// <summary>
        /// Raises events marked as required during the tree processing.
        /// </summary>
        private void RaisePendingEvents()
        {
            if ((events & PendingEvents.SelectionChanged) != 0)
                OnSelectionChanged();

            if ((events & PendingEvents.FocusChanged) != 0)
                OnFocusedNodeChanged();

            if ((events & PendingEvents.CheckedNodesChanged) != 0)
                OnCheckedNodesChanged();

            events = 0;
        }

        /// <summary>
        /// Forces every measured node to be measured again. Method
        /// is called, when a change is introduced, which influences
        /// metrics of elements.
        /// </summary>
        /// <remarks>Very slow. Should not be called after populating 
        /// the tree.</remarks>
        private void Remeasure()
        {
            Remeasure(root);
        }

        /// <summary>
        /// Forces the chosen node to be measured again.
        /// </summary>
        /// <remarks>Very slow. Should not be called after populating 
        /// the tree.</remarks>
        private void Remeasure(VirtualNode node)
        {
            VisualTreeUpdating();
            renderer.Invalidate();

            DoRemeasure(node);

            InvalidateMetrics();
        }

        /// <summary>
        /// Scrolls the tree to specified y offset
        /// </summary>
        private void ScrollTo(int newYOffset)
        {
            states |= TreeViewStates.ManualScrolling;
            try
            {
                yOffset = newYOffset;
                vScrollBar.Value = newYOffset;
                InvalidateMetrics();
            }
            finally
            {
                states &= ~TreeViewStates.ManualScrolling;
            }
        }

        /// <summary>
        /// Sets an event as required to be raised after finishing of tree processing
        /// </summary>
        /// <param name="pendingEvents">Required event</param>
        private void SetPendingEvent(PendingEvents pendingEvents)
        {
            events |= pendingEvents;
        }

        /// <summary>
        /// Positions and initializes properties of horizontal scrollbar
        /// </summary>
        private void SetupHorizontalScrollbar(bool vScrollBarVisible, bool hScrollBarVisible)
        {
            if (hScrollBarVisible)
            {
                hScrollBar.Anchor = AnchorStyles.Left | AnchorStyles.Bottom | AnchorStyles.Right;
                hScrollBar.Left = contentArea.Left;
                hScrollBar.Top = contentArea.Bottom - hScrollBar.Height;
                hScrollBar.Width = vScrollBarVisible ? contentArea.Width - vScrollBar.Width : contentArea.Width;
                hScrollBar.Visible = true;

                if (maxXOffset > 0)
                {
                    hScrollBar.Minimum = 0;
                    hScrollBar.Maximum = (int)maxXOffset + contentArea.Width - 1;
                    hScrollBar.LargeChange = Math.Max(0, contentArea.Width);
                    hScrollBar.SmallChange = SCROLLBAR_SMALL_CHANGE;
                    hScrollBar.Value = xOffset;
                }

                states |= TreeViewStates.HScrollVisible;
            }
            else
            {
                hScrollBar.Visible = false;
                states &= ~TreeViewStates.HScrollVisible;
            }
        }

        /// <summary>
        /// Positions and initializes properties of vertical scrollbar
        /// </summary>
        private void SetupVertivalScrollbar(bool vScrollBarVisible, bool hScrollBarVisible)
        {
            if (vScrollBarVisible)
            {
                vScrollBar.Anchor = AnchorStyles.Right | AnchorStyles.Top | AnchorStyles.Bottom;
                vScrollBar.Left = contentArea.Right - vScrollBar.Width;
                vScrollBar.Top = contentArea.Top;
                vScrollBar.Height = hScrollBarVisible ? contentArea.Height - hScrollBar.Height : contentArea.Height;
                vScrollBar.Visible = true;

                if (maxYOffset > 0)
                {
                    vScrollBar.Minimum = 0;
                    vScrollBar.Maximum = (int)maxYOffset + contentArea.Height - 1;
                    vScrollBar.LargeChange = Math.Max(0, contentArea.Height);
                    vScrollBar.SmallChange = (int)renderer.GetDefaultNodeHeight();
                    vScrollBar.Value = yOffset;
                }

                states |= TreeViewStates.VScrollVisible;
            }
            else
            {
                vScrollBar.Visible = false;
                states &= ~TreeViewStates.VScrollVisible;
            }
        }

        /// <summary>
        /// Makes sure, that children for given node are initialized.
        /// </summary>
        /// <remarks>Does not set any tree flags</remarks>
        private void VerifyChildrenInitialized(VirtualNode node)
        {
            node.CheckNull("node");
            node.CheckOwnerTree(this, "node");

            if (node.HasChildren && node.ChildCount == 0)
            {
                if (GetChildCount != null)
                {
                    GetChildCountEventArgs args = new GetChildCountEventArgs(node);
                    OnGetChildCount(args);

                    DoSetChildCount(node, args.ChildCount);

                    if (node.IsExpanded)
                    {
                        bool nodeVisible = TreeStructure.IsNodeFullyVisible(node);
                        
                        if (nodeVisible)
                            VisualTreeUpdating();

                        VerifyChildrenMeasured(node);

                        if (nodeVisible)
                        {
                            InvalidateMetrics();
                        }
                    }
                }
                else
                    DoSetChildCount(node, 0);
            }
        }

        /// <summary>
        /// Makes sure, that children for given node are measured.
        /// </summary>
        /// <remarks>Does not set any tree flags</remarks>
        private void VerifyChildrenMeasured(VirtualNode node)
        {
            var child = node.FirstChild;
            while (child != null)
            {
                VerifyNodeUserInitialized(child);
                VerifyNodeMeasured(child);

                child = child.NextSibling;
            }
        }

        /// <summary>
        /// Makes sure, that tree's metrics are measured.
        /// </summary>
        private void VerifyMetrics()
        {
            if ((states & TreeViewStates.Measured) == 0)
                EvalMetrics();
        }

        /// <summary>
        /// Verifies, that user has initialized the node with some
        /// basic informations.
        /// </summary>
        private void VerifyNodeUserInitialized(VirtualNode node)
        {
            node.CheckNull("node");
            node.CheckOwnerTree(this, "node");

            if (!node.IsUserInitiated)
                InternalInitNode(node);
        }

        /// <summary>
        /// Evaluates node's height (optionally asking class 
        /// user to provide one)
        /// </summary>
        private void VerifyNodeMeasured(VirtualNode node)
        {
            node.CheckNull("node");
            node.CheckOwnerTree(this, "node");

            if (node == root)
                throw new InvalidOperationException("Internal error: cannot measure hidden root node!");

            if (!node.IsMeasured)
                InternalMeasureNode(node);
        }

        /// <summary>
        /// Called, when tree is to be updated. Stops the
        /// position cache thread and clears the cache.
        /// </summary>
        private void VisualTreeUpdating()
        {
            if ((states & TreeViewStates.NeedUpdateCache) == 0)
            {
                states |= TreeViewStates.NeedUpdateCache;

                positionCache.Invalidate();

                // BeginInvoke can be called only if window handle is created.
                // If the handle is not created, VisualTreeUpdated will be
                // called in HandleCreated override.
                if (this.IsHandleCreated)
                    this.BeginInvoke(new InvokeDelegate(VisualTreeUpdated));
            }
        }

        /// <summary>
        /// Schedules creation of the position cache.
        /// </summary>
        private void VisualTreeUpdated()
        {
            if ((states & TreeViewStates.NeedUpdateCache) != 0)
            {
                states &= ~TreeViewStates.NeedUpdateCache;
                positionCache.CreateCache(root);
            }
        }

        // Private Event handlers --------------------------------------------

        /// <summary>
        /// Handler for vertical scrollbar scroll event
        /// </summary>
        private void vScrollBar_Scroll(object sender, ScrollEventArgs e)
        {
            if ((states & (TreeViewStates.Creating | TreeViewStates.Measuring | TreeViewStates.ManualScrolling)) != 0)
                return;

            switch (e.Type)
            {
                case ScrollEventType.ThumbTrack:
                    {
                        states |= TreeViewStates.Scrolling;
                        break;
                    }
                case ScrollEventType.EndScroll:
                    {
                        states &= ~TreeViewStates.Scrolling;
                        break;
                    }
                default:
                    {
                        // No action required
                        break;
                    }
            }

            yOffset = e.NewValue;
            InvalidateMetrics();
        }

        /// <summary>
        /// Handler for horizontal scrollbar scroll event
        /// </summary>
        private void hScrollBar_Scroll(object sender, ScrollEventArgs e)
        {
            if ((states & (TreeViewStates.Creating | TreeViewStates.Measuring | TreeViewStates.ManualScrolling)) != 0)
                return;

            switch (e.Type)
            {
                case ScrollEventType.ThumbTrack:
                    {
                        states |= TreeViewStates.Scrolling;
                        break;
                    }
                case ScrollEventType.EndScroll:
                    {
                        states &= ~TreeViewStates.Scrolling;
                        break;
                    }
                default:
                    {
                        // No action required
                        break;
                    }
            }

            xOffset = e.NewValue;
            InvalidateMetrics();
        }

        /// <summary>
        /// Handler for recreation of image list's handle (raised when size 
        /// of image list's images is changed)
        /// </summary>
        private void images_RecreateHandle(object sender, EventArgs e)
        {
            Remeasure();
        }

        /// <summary>
        /// Handler for autoscroll timer event
        /// </summary>
        private void scrollTimer_Tick(object sender, EventArgs e)
        {
            switch (scrollDirection)
            {
                case ScrollDirection.Up:
                    {
                        int newYOffset = Math.Max(0, yOffset - AUTO_SCROLL_VALUE);
                        ScrollTo(newYOffset);
                        break;
                    }
                case ScrollDirection.Down:
                    {
                        int newYOffset = Math.Min(maxYOffset, yOffset + AUTO_SCROLL_VALUE);
                        ScrollTo(newYOffset);
                        break;
                    }
                default:
                    throw new ArgumentException("Unsupported scroll direction!");
            }

            Point localMousePos = PointToClient(Cursor.Position);
            DoHandleRectangularSelection(localMousePos.X, localMousePos.Y);
            
            // No hottrack while rectangular selection is being done
        }

        // Protected fields --------------------------------------------------

        protected NodeProxies node;

        // Protected methods -------------------------------------------------

        protected override void OnGotFocus(EventArgs e)
        {
            Invalidate();
            base.OnGotFocus(e);
        }

        protected override void OnLostFocus(EventArgs e)
        {
            Invalidate();
            base.OnLostFocus(e);
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            VerifyMetrics();

            try
            {
                states |= TreeViewStates.Painting;

                // Background
                e.Graphics.FillRectangle(SystemBrushes.Window, this.ClientRectangle);

                // Nodes
                for (int i = 0; i < visuals.Count; i++)
                {
                    Rectangle itemClip = Rectangle.Intersect(visuals[i].Metrics.NodeArea, drawableArea);
                    if (!itemClip.IsEmpty)
                        renderer.DrawVisual(e.Graphics, itemClip, visuals[i]);
                }

                // Rectangular selection
                if (userInteraction == UserInteraction.RectangularSelection)
                {
                    if (Application.RenderWithVisualStyles)
                    {
                        Rectangle mouseRectangle = GetMouseSelectionRect(CoordinateSystem.Client);
                        mouseRectangle.Width -= 1;
                        mouseRectangle.Height -= 1;

                        using (Brush b = new SolidBrush(Spk.Controls.Common.Drawing.SetOpacity(SystemColors.Highlight, 128)))
                            e.Graphics.FillRectangle(b, mouseRectangle);
                        e.Graphics.DrawRectangle(SystemPens.Highlight, mouseRectangle);
                    }
                    else
                    {
                        Rectangle mouseRectangle = GetMouseSelectionRect(CoordinateSystem.Client);
                        ControlPaint.DrawFocusRectangle(e.Graphics, mouseRectangle);
                    }
                }

                // Border
                if (showBorder)
                    e.Graphics.DrawRectangle(SystemPens.ControlDark, new Rectangle(ClientRectangle.Left, ClientRectangle.Top, ClientRectangle.Width - 1, ClientRectangle.Height - 1));
            }
            finally
            {
                states &= ~TreeViewStates.Painting;
            }
        }

        protected override void OnResize(EventArgs e)
        {
            base.OnResize(e);
            InvalidateMetrics();
        }

        protected override void OnMouseDown(MouseEventArgs e)
        {
            VerifyMetrics();

            if (drawableArea.Contains(e.Location) && userInteraction == UserInteraction.Idle)
            {
                int visualIndex = GetVisualIndexAt(e.Location);

                Action startRectangularSelection = () =>
                    {
                        if ((ModifierKeys & Keys.Control) == 0)
                        {
                            DoClearSelection();
                            DoClearShiftSelectionOrigin();
                        }

                        // Area outside node's active elements was hit - starting rectangular selection
                        if (multiSelect)
                        {
                            userInteraction = UserInteraction.RectangularSelection;
                            mouseSelectionStart = RelativeToAbsolute(ClientToRelative(new Point(e.X, e.Y)));
                            mouseSelectionEnd = mouseSelectionStart;
                        }
                    };

                if (visualIndex >= 0)
                {
                    if (e.Button == MouseButtons.Left)
                    {
                        if (visuals[visualIndex].Visual.ExpandMarkArea.Contains(e.Location))
                        {
                            // Expand mark of item was hit
                            // Modifier keys does not matter, action is always the same
                            if (visuals[visualIndex].General.Node.HasChildren && allowExpandCollapse)
                                DoToggleNode(visuals[visualIndex].General.Node);
                        }
                        else if (visuals[visualIndex].Visual.CheckboxArea.Contains(e.Location))
                        {
                            // Checkbox area of item was hit

                            var hitNode = visuals[visualIndex].General.Node;

                            DoSetNodeChecked(hitNode, !hitNode.IsChecked);

                            InvalidateMetrics();
                        }
                        else if (visuals[visualIndex].Visual.ContentArea.Contains(e.Location))
                        {
                            // Item area of item was hit

                            var hitNode = visuals[visualIndex].General.Node;

                            DoClickNode(hitNode, (ModifierKeys & Keys.Shift) != 0, (ModifierKeys & Keys.Control) != 0);
                        }
                        else
                        {
                            startRectangularSelection();
                        }
                    }
                }
                else
                {
                    startRectangularSelection();
                }
            }
            

            RaisePendingEvents();

            base.OnMouseDown(e);
        }

        protected override void OnMouseMove(MouseEventArgs e)
        {
            if (userInteraction == UserInteraction.RectangularSelection)
            {
                if (e.Y >= this.ClientRectangle.Height)
                {
                    if (!scrollTimer.Enabled)
                    {
                        scrollDirection = ScrollDirection.Down;
                        scrollTimer.Start();
                    }

                    if (e.Y - this.ClientRectangle.Height > FAST_SCROLL_DISTANCE)
                        scrollTimer.Interval = 25;
                    else
                        scrollTimer.Interval = 100;
                }
                else if (e.Y < 0)
                {
                    if (!scrollTimer.Enabled)
                    {
                        scrollDirection = ScrollDirection.Up;
                        scrollTimer.Start();
                    }

                    if (-e.Y > FAST_SCROLL_DISTANCE)
                        scrollTimer.Interval = 25;
                    else
                        scrollTimer.Interval = 100;               
                }
                else if (e.Y >= 0 && e.Y < this.ClientRectangle.Height && scrollTimer.Enabled)
                {
                    scrollTimer.Stop();
                }

                // Converting client coordinates to relative ones
                DoHandleRectangularSelection(e.X, e.Y);

                Invalidate();
            }
            else if (userInteraction == UserInteraction.Idle)
            {
                if (hotTrack)
                {
                    DoHandleHotTrack(e.X, e.Y);

                    Invalidate();
                }
            }

            base.OnMouseMove(e);
        }

        protected override void OnMouseUp(MouseEventArgs e)
        {
            if (userInteraction == UserInteraction.RectangularSelection)
            {
                if (e.Button == System.Windows.Forms.MouseButtons.Left)
                {
                    scrollTimer.Stop();

                    // Process selection
                    ApplyRectangularSelection();

                    mouseSelectionStart = new Point(-1, -1);
                    mouseSelectionEnd = new Point(-1, -1);
                    userInteraction = UserInteraction.Idle;

                    DoClearRectangularSelection();

                    if (hotTrack)
                        DoHandleHotTrack(e.X, e.Y);

                    Invalidate();
                }
            }

            RaisePendingEvents();

            base.OnMouseUp(e);
        }

        protected override void OnMouseWheel(MouseEventArgs e)
        {
            int newYOffset;

            if (e.Delta > 0)
            {
                // Scroll up
                newYOffset = yOffset - 3 * (int)renderer.GetDefaultNodeHeight();
                if (newYOffset < 0)
                    newYOffset = 0;
            }
            else
            {
                // Scroll down
                newYOffset = yOffset + 3 * (int)renderer.GetDefaultNodeHeight();
                if (newYOffset > maxYOffset)
                    newYOffset = maxYOffset;
            }

            ScrollTo(newYOffset);

            if (userInteraction == UserInteraction.RectangularSelection)
            {
                DoHandleRectangularSelection(e.X, e.Y);
            }
            else
            {
                if (hotTrack)
                    DoHandleHotTrack(e.X, e.Y);
            }
        }

        protected override void OnMouseLeave(EventArgs e)
        {
            DoClearHotTrack();

            base.OnMouseLeave(e);
        }

        protected override void OnKeyDown(KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Up || e.KeyCode == Keys.Down || e.KeyCode == Keys.PageUp || e.KeyCode == Keys.PageDown || e.KeyCode == Keys.Home || e.KeyCode == Keys.End)
            {
                #region Up, down, page up, page down, home, end

                // Common action for all navigation keys. Sets focus
                // to specified node and processes selection basing
                // on state of modifier keys
                #region focusNode

                Action<VirtualNode> focusNode = (VirtualNode node) =>
                {
                    DoSetFocusedNode(node);

                    if (multiSelect)
                    {
                        if (e.Shift && e.Control)
                        {
                            if (shiftSelectionOrigin != null)
                                DoModifySelectionRange(shiftSelectionOrigin, focusedNode, shiftSelectionOrigin.IsSelected);
                            else
                            {
                                DoSetSelection(focusedNode);
                                DoSetShiftSelectionOrigin(focusedNode);
                            }
                        }
                        else if (e.Shift)
                        {
                            if (shiftSelectionOrigin != null)
                            {
                                DoModifySelectionRange(shiftSelectionOrigin, focusedNode);
                            }
                            else
                            {
                                DoSetSelection(focusedNode);
                                DoSetShiftSelectionOrigin(focusedNode);
                            }
                        }
                        else if (!e.Control)
                        {
                            DoSetSelection(focusedNode);
                            DoSetShiftSelectionOrigin(focusedNode);
                        }

                    }
                    else
                    {
                        if (!e.Control)
                        {
                            DoSetSelection(focusedNode);
                            DoSetShiftSelectionOrigin(focusedNode);
                        }
                    }
                };

                #endregion

                if (focusedNode != null)
                {
                    if (e.KeyCode == Keys.Up)
                    {
                        VirtualNode nextNode = TreeStructure.GetPreviousVisible(focusedNode);

                        if (nextNode != null)
                        {
                            focusNode(nextNode);
                            DoScrollToNode(focusedNode);
                        }
                    }
                    else if (e.KeyCode == Keys.Down)
                    {
                        VirtualNode nextNode = TreeStructure.GetNextVisible(focusedNode);

                        if (nextNode != null)
                        {
                            focusNode(nextNode);
                            DoScrollToNode(focusedNode);
                        }
                    }
                    else if (e.KeyCode == Keys.PageUp)
                    {
                        Action<int> pageUp = (int newYOffset) =>
                            {
                                if (newYOffset != yOffset)
                                {
                                    VirtualNode tempNextNode = GetFirstFullyVisible(newYOffset, drawableArea.Height);

                                    if (tempNextNode != null && TreeStructure.Compare(tempNextNode, focusedNode) == NodeVisualRelationship.Earlier)
                                    {
                                        VirtualNode nextNode = tempNextNode;
                                        focusNode(nextNode);
                                    }

                                    ScrollTo(newYOffset);
                                }
                            };

                        if (GetNodeRelativeLocation(focusedNode) == NodeRelativeLocation.Inside)
                        {
                            VirtualNode currentFirstNode = GetFirstFullyVisible();

                            if (currentFirstNode == null)
                                throw new InvalidOperationException("Internal error: first fully visible node not found!");

                            if (TreeStructure.Compare(currentFirstNode, focusedNode) == NodeVisualRelationship.Earlier)
                            {
                                VirtualNode nextNode = currentFirstNode;
                                focusNode(nextNode);
                            }
                            else
                                pageUp(Math.Max(0, yOffset - drawableArea.Height));
                        }
                        else
                            pageUp(Math.Max(0, EvalNodeYOffset(focusedNode) - drawableArea.Height));
                    }
                    else if (e.KeyCode == Keys.PageDown)
                    {
                        Action<int> pageDown = (int newYOffset) =>
                            {
                                if (newYOffset != yOffset)
                                {
                                    VirtualNode tempNextNode = GetLastFullyVisible(newYOffset, drawableArea.Height);
                                    if (tempNextNode != null && TreeStructure.Compare(tempNextNode, focusedNode) == NodeVisualRelationship.Later)
                                    {
                                        VirtualNode nextNode = tempNextNode;
                                        focusNode(nextNode);
                                    }
                                }

                                ScrollTo(newYOffset);
                            };

                        if (GetNodeRelativeLocation(focusedNode) == NodeRelativeLocation.Inside)
                        {
                            VirtualNode currentLastNode = GetLastFullyVisible();

                            if (currentLastNode == null)
                                throw new InvalidOperationException("Internal error: last fully visible node not found!");

                            if (TreeStructure.Compare(currentLastNode, focusedNode) == NodeVisualRelationship.Later)
                            {
                                VirtualNode nextNode = currentLastNode;
                                focusNode(nextNode);
                            }
                            else
                                pageDown(Math.Min(maxYOffset, yOffset + drawableArea.Height));
                        }
                        else
                            pageDown(Math.Min(maxYOffset, EvalNodeYOffset(focusedNode) + drawableArea.Height));
                    }
                    else if (e.KeyCode == Keys.Home)
                    {
                        VirtualNode nextNode = TreeStructure.GetFirstVisible(root);
                        if (nextNode != focusedNode)
                        {
                            focusNode(nextNode);
                        }

                        ScrollTo(0);
                    }
                    else if (e.KeyCode == Keys.End)
                    {
                        VirtualNode nextNode = TreeStructure.GetLastVisible(root);
                        if (nextNode != focusedNode)
                        {
                            focusNode(nextNode);
                        }

                        ScrollTo(maxYOffset);
                    }
                }

                RaisePendingEvents();

                #endregion
            }
            else if (e.KeyCode == Keys.Right)
            {
                #region Right

                if (focusedNode != null)
                {
                    if (focusedNode.IsExpanded)
                    {
                        VirtualNode nextNode = TreeStructure.GetFirstVisibleChild(focusedNode);
                        if (nextNode != null)
                        {
                            DoSetFocusedNode(nextNode);
                            if (!e.Control)
                            {
                                DoSetSelection(focusedNode);
                                DoSetShiftSelectionOrigin(focusedNode);
                            }
                        }
                    }
                    else if (focusedNode.HasChildren && allowExpandCollapse)
                    {
                        DoExpandNode(focusedNode);
                    }

                    DoScrollToNode(focusedNode);
                }

                RaisePendingEvents();

                #endregion
            }
            else if (e.KeyCode == Keys.Left)
            {
                #region Left

                if (focusedNode != null)
                {
                    if (!focusedNode.IsExpanded || !allowExpandCollapse)
                    {
                        if (focusedNode.Parent != root)
                        {
                            DoSetFocusedNode(focusedNode.Parent);
                            if (!e.Control)
                            {
                                DoSetSelection(focusedNode);
                                DoSetShiftSelectionOrigin(focusedNode);
                            }
                        }
                    }
                    else if (allowExpandCollapse)
                    {
                        DoCollapseNode(focusedNode);
                    }

                    DoScrollToNode(focusedNode);
                }

                RaisePendingEvents();

                #endregion
            }
            else if (e.KeyCode == Keys.Space)
            {
                #region Space

                if (focusedNode != null)
                    DoClickNode(focusedNode, e.Shift, e.Control);

                RaisePendingEvents();

                #endregion
            }
            else
                base.OnKeyDown(e);
        }

        protected override void OnFontChanged(EventArgs e)
        {
            VisualTreeUpdating();

            Remeasure();

            InvalidateMetrics();

            base.OnFontChanged(e);
        }

        protected override void OnHandleCreated(EventArgs e)
        {
            base.OnHandleCreated(e);
            VisualTreeUpdated();
        }

        protected override bool IsInputKey(Keys keyData)
        {
            // Capture arrow keys
            if ((keyData & (Keys.Up | Keys.Down | Keys.Left | Keys.Right | Keys.PageDown | Keys.PageUp | Keys.ControlKey | Keys.Control)) != 0)
                return true;
            else
                return base.IsInputKey(keyData);
        }

        protected override void WndProc(ref Message m)
        {
            if (m.Msg == WM_THEMECHANGED)
                Remeasure();

            base.WndProc(ref m);
        }

        protected virtual void OnCheckedNodesChanged()
        {
            if (CheckedNodesChanged != null)
                CheckedNodesChanged(this, new EventArgs());
        }

        protected virtual void OnGetNodeInitialData(InitNodeEventArgs e)
        {
            if (InitNode != null)
                InitNode(this, e);
        }

        protected virtual void OnGetNodeRegions(GetNodeRegionsEventArgs e)
        {
            if (GetNodeRegions != null)
                GetNodeRegions(this, e);
        }

        protected virtual void OnGetChildCount(GetChildCountEventArgs e)
        {
            if (GetChildCount != null)
                GetChildCount(this, e);
        }

        protected virtual void OnGetText(GetTextEventArgs e)
        {
            if (GetText != null)
                GetText(this, e);
        }

        protected virtual void OnGetImageIndex(GetImageIndexEventArgs e)
        {
            if (GetImageIndex != null)
                GetImageIndex(this, e);
        }

        protected virtual void OnNodeCollapsing(NodeEventArgs e)
        {
            if (NodeCollapsing != null)
                NodeCollapsing(this, e);
        }

        protected virtual void OnNodeCollapsed(NodeEventArgs e)
        {
            if (NodeCollapsed != null)
                NodeCollapsed(this, e);
        }

        protected virtual void OnNodeExpanding(NodeEventArgs e)
        {
            if (NodeExpanding != null)
                NodeExpanding(this, e);
        }

        protected virtual void OnNodeExpanded(NodeEventArgs e)
        {
            if (NodeExpanded != null)
                NodeExpanded(this, e);
        }

        protected virtual void OnNodeClicked(NodeEventArgs e)
        {
            if (NodeClicked != null)
                NodeClicked(this, e);
        }

        protected virtual void OnNodeDoubleClicked(NodeEventArgs e)
        {
            if (NodeDoubleClicked != null)
                NodeDoubleClicked(this, e);
        }

        protected virtual void OnGetNodeHeight(GetNodeHeightEventArgs e)
        {
            if (GetNodeHeight != null)
                GetNodeHeight(this, e);
        }

        protected virtual void OnGetNodeWidth(GetNodeWidthEventArgs e)
        {
            if (GetNodeWidth != null)
                GetNodeWidth(this, e);
        }

        protected virtual void OnGetNodeContentsWidth(GetNodeContentsWidthEventArgs e)
        {
            if (GetNodeContentsWidth != null)
                GetNodeContentsWidth(this, e);
        }

        protected virtual void OnDrawNodeContents(DrawNodeContentsEventArgs e)
        {
            if (DrawNodeContents != null)
                DrawNodeContents(this, e);
        }

        protected virtual void OnDrawNode(DrawNodeEventArgs e)
        {
            if (DrawNode != null)
                DrawNode(this, e);
        }

        protected virtual void OnSelectionChanged()
        {
            if (SelectionChanged != null)
                SelectionChanged(this, new EventArgs());
        }

        protected virtual void OnShowNodeCheckbox(GetNodeCheckboxVisibilityEventArgs e)
        {
            if (GetNodeCheckboxVisibility != null)
                GetNodeCheckboxVisibility(this, e);
        }

        protected virtual void OnFocusedNodeChanged()
        {
            if (FocusedNodeChanged != null)
                FocusedNodeChanged(this, new EventArgs());
        }
 
        // Public methods ----------------------------------------------------

        /// <summary>
        /// Constructor
        /// </summary>
        public VirtualTreeView()
        {
            // Low-level control settings
            SetStyle(ControlStyles.Selectable, true);

            // Visuals
            visuals = new List<NodePaintInfo>();
            images = null;
            renderer = new DefaultNodeRenderer(this);

            // Internals
            selectedNodes = new List<VirtualNode>();
            selectedNodesRO = new ReadOnlyCollection<VirtualNode>(selectedNodes);
            checkedNodes = new List<VirtualNode>();
            checkedNodesRO = new ReadOnlyCollection<VirtualNode>(checkedNodes);
            positionCache = new PositionCache();
            focusedNode = null;

            // Interaction
            userInteraction = UserInteraction.Idle;
            mouseSelectionStart = new Point(-1, -1);
            mouseSelectionEnd = new Point(-1, -1);
            scrollTimer = new Timer();
            scrollTimer.Interval = 100;
            scrollTimer.Tick += scrollTimer_Tick;
            scrollDirection = ScrollDirection.Up;

            // States and events
            states = 0;
            events = 0;
            
            // Proxies
            node = new NodeProxies(this);

            // Visuals
            allowExpandCollapse = true;
            showBorder = true;
            showLines = true;
            showRootLines = true;
            showExpandMarks = true;
            showCheckboxes = false;
            showImages = true;
            hotTrack = false;

            // Initializing

            states = TreeViewStates.Creating;
            try
            {
                root = new VirtualNode()
                {
                    OwnerTree = this,
                    HasChildren = true,
                    IsExpanded = true,
                    IsMeasured = true,
                    IsVisible = true
                };

                yOffset = 0;
                maxYOffset = 0;
                drawableArea = new Rectangle(0, 0, 0, 0);

                // Scrollbars

                vScrollBar = new VTVVerticalScrollBar();
                vScrollBar.Visible = false;
                vScrollBar.Scroll += vScrollBar_Scroll;
                this.Controls.Add(vScrollBar);

                hScrollBar = new VTVHorizontalScrollBar();
                hScrollBar.Visible = false;
                hScrollBar.Scroll += hScrollBar_Scroll;
                this.Controls.Add(hScrollBar);

                // Misc properties

                this.DoubleBuffered = true;
                this.ResizeRedraw = true;

                // Visuals
                InvalidateMetrics();
            }
            finally
            {
                states &= ~TreeViewStates.Creating;
            }
        }

        /// <summary>
        /// Adds node to selection
        /// </summary>
        public void AddToSelection(VirtualNode node)
        {
            DoAddToSelection(node);
            RaisePendingEvents();
        }

        public VirtualNode AddChildNode(VirtualNode node)
        {
            node.CheckNull("node");
            node.CheckOwnerTree(this, "node");

            var result = DoAddChild(node);
            RaisePendingEvents();

            return result;
        }

        /// <summary>
        /// Clears selection
        /// </summary>
        private void ClearSelection()
        {
            DoClearSelection();
            RaisePendingEvents();
        }

        /// <summary>
        /// Deletes the node
        /// </summary>
        public void DeleteNode(VirtualNode node)
        {
            node.CheckNull("node");
            node.CheckOwnerTree(this, "node");

            DoDeleteNode(node);
            RaisePendingEvents();
        }

        /// <summary>
        /// Inserts node before given node (as previous sibling)
        /// </summary>
        public VirtualNode InsertNodeBefore(VirtualNode node)
        {
            node.CheckNull("node");
            node.CheckOwnerTree(this, "node");

            var result = DoInsertNodeBefore(node);
            RaisePendingEvents();
            
            return result;
        }

        /// <summary>
        /// Inserts node after given node (as next sibling)
        /// </summary>
        public VirtualNode InsertNodeAfter(VirtualNode node)
        {
            node.CheckNull("node");
            node.CheckOwnerTree(this, "node");

            var result = DoInsertNodeAfter(node);
            RaisePendingEvents();

            return result;
        }

        /// <summary>
        /// Deletes all node's children
        /// </summary>
        public void DeleteChildren(VirtualNode node)
        {
            node.CheckNull("node");
            node.CheckOwnerTree(this, "node");

            DoDeleteChildren(node);
            RaisePendingEvents();
        }

        /// <summary>
        /// Releases any native or managed resources used
        /// by the control.
        /// </summary>
        protected override void Dispose(bool disposing)
        {
            if (renderer != null)
            {
                renderer.Dispose();
                renderer = null;
            }

            base.Dispose(disposing);
        }

        /// <summary>
        /// Expands the node
        /// </summary>
        public void ExpandNode(VirtualNode node)
        {
            node.CheckNull("node");
            node.CheckOwnerTree(this, "node");

            DoExpandNode(node);
        }

        /// <summary>
        /// Collapses the node
        /// </summary>
        public void CollapseNode(VirtualNode node)
        {
            node.CheckNull("node");
            node.CheckOwnerTree(this, "node");

            DoCollapseNode(node);
            RaisePendingEvents();
        }

        /// <summary>
        /// Find node, which holds specific data
        /// </summary>
        public VirtualNode FindNodeByData(object data)
        {
            return InternalFindNodeByData(root, data);
        }

        /// <summary>
        /// Returns child with specified index
        /// </summary>
        /// <remarks>
        /// This method iterates through node's children and is very slow.
        /// Using it to access nodes frequently is not advised.
        /// </remarks>
        public VirtualNode GetChild(VirtualNode node, int index)
        {
            if (index < 0 || index >= node.ChildCount)
                throw new IndexOutOfRangeException("index");

            VirtualNode current;

            if (index < node.ChildCount / 2)
            {
                current = node.FirstChild;
                while (current.Index < index)
                    current = current.NextSibling;              
            }
            else
            {
                current = node.LastChild;
                while (current.Index > index)
                    current = current.PreviousSibling;
            }

            return current;
        }

        /// <summary>
        /// Hides the node
        /// </summary>
        public void HideNode(VirtualNode node)
        {
            node.CheckNull("node");
            node.CheckOwnerTree(this, "node");

            DoHideNode(node);
            RaisePendingEvents();
        }

        /// <summary>
        /// Force the Virtual Treeview to rebuild the visuals cache
        /// (items, which are currently displayed). Use, if one of
        /// (potentially) visible items changed its appearance (for
        /// example, text)
        /// </summary>
        public void RefreshVisuals()
        {
            InvalidateMetrics();
        }

        /// <summary>
        /// Forces the node to be measured again.
        /// </summary>
        public void RemeasureNode(VirtualNode node)
        {
            node.CheckNull("node");
            node.CheckOwnerTree(this, "node");

            Remeasure(node);
            RaisePendingEvents();
        }

        /// <summary>
        /// Deselects the node
        /// </summary>
        public void RemoveFromSelection(VirtualNode node)
        {
            node.CheckNull("node");
            node.CheckOwnerTree(this, "node");

            DoRemoveFromSelection(node);
            RaisePendingEvents();
        }
   
        /// <summary>
        /// Removes from selection node with specified index on
        /// selected node list
        /// </summary>
        /// <param name="selectedNodeIndex"></param>
        public void RemoveFromSelection(int selectedNodeIndex)
        {
            if (selectedNodeIndex < 0 || selectedNodeIndex >= selectedNodes.Count)
                throw new ArgumentOutOfRangeException("selectedNodeIndex");

            DoRemoveFromSelection(selectedNodeIndex);
            RaisePendingEvents();
        }

        /// <summary>
        /// Removes node and its children from selection
        /// </summary>
        /// <param name="node"></param>
        public void RemoveFromSelectionRecursive(VirtualNode node)
        {
            node.CheckNull("node");
            node.CheckOwnerTree(this, "node");

            DoRemoveFromSelectionRecursive(node);
            RaisePendingEvents();
        }

        /// <summary>
        /// Sets the checked state of node to specified value
        /// </summary>
        public void SetChecked(VirtualNode node, bool value)
        {
            DoSetNodeChecked(node, value);
        }

        /// <summary>
        /// Sets child count for specified node
        /// </summary>
        public void SetChildCount(VirtualNode node, uint value)
        {
            node.CheckNull("node");
            node.CheckOwnerTree(this, "node");

            DoSetChildCount(node, value);
            RaisePendingEvents();
        }

        /// <summary>
        /// Sets height of node
        /// </summary>
        /// <remarks>
        /// Works only in ownerdraw mode.
        /// </remarks>
        public void SetNodeHeight(VirtualNode node, uint value)
        {
            node.CheckNull("node");
            node.CheckOwnerTree(this, "node");

            if (drawMode == DrawModes.Default || drawMode == DrawModes.System)
                throw new VirtualTreeViewException("Cannot set node height when draw mode is set to default!");

            DoSetNodeHeight(node, value);
        }

        /// <summary>
        /// Sets the selection to the single node
        /// </summary>
        public void SetSelection(VirtualNode node)
        {
            node.CheckNull("node");
            node.CheckOwnerTree(this, "node");

            DoSetSelection(node);
            RaisePendingEvents();
        }

        public bool SetSelectionByData(object data)
        {
            var node = FindNodeByData(data);

            if (node != null)
            {
                SetSelection(node);
                return true;
            }
            else
                return false;
        }

        /// <summary>
        /// Sets the selection to item, which holds specific data
        /// </summary>
        public bool SetSelectionByData(VirtualNode root, object data)
        {
            if (root == null)
                throw new ArgumentNullException("root");

            var child = root.FirstChild;
            while (child != null && child.Data != data)
                child = child.NextSibling;

            if (child != null)
            {
                SetSelection(child);
                return true;
            }
            else
                return false;
        }

        /// <summary>
        /// Selects range of visible nodes
        /// </summary>
        public void SetSelection(VirtualNode first, VirtualNode last)
        {
            first.CheckNull("first");
            first.CheckOwnerTree(this, "first");
            last.CheckNull("last");
            last.CheckOwnerTree(this, "last");

            DoClearSelection();
            DoModifySelectionRange(first, last);

            RaisePendingEvents();
        }

        /// <summary>
        /// Scrolls the tree, such that passed node is visible.
        /// </summary>
        public void ScrollToNode(VirtualNode node)
        {
            node.CheckNull("node");
            node.CheckOwnerTree(this, "node");

            if (!TreeStructure.IsNodeFullyVisible(node))
                throw new VirtualTreeViewException("Cannot scroll to invisible node!");

            DoScrollToNode(node);
        }

        /// <summary>
        /// Shows the node
        /// </summary>
        public void ShowNode(VirtualNode node)
        {
            node.CheckNull("node");
            node.CheckOwnerTree(this, "node");

            DoShowNode(node);
        }

        /// <summary>
        /// Toggles node.
        /// </summary>
        public void ToggleNode(VirtualNode node)
        {
            node.CheckNull("node");
            node.CheckOwnerTree(this, "node");

            DoToggleNode(node);
            RaisePendingEvents();
        }

        // Public properties -------------------------------------------------

        /// <summary>
        /// Access to currently focused node
        /// </summary>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public VirtualNode FocusedNode
        {
            get
            {
                return focusedNode;
            }
            set
            {
                if (value == null && root.FirstChild != null)
                    throw new VirtualTreeViewException("Focused node can be null only if tree has no root nodes!");

                value.CheckOwnerTree(this, "value");

                DoSetFocusedNode(value);
            }
        }

        /// <summary>
        /// Node, which is currently under the mouse cursor
        /// </summary>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public VirtualNode HotTrackNode
        {
            get
            {
                if (hotTrackVisual != null)
                    return hotTrackVisual.General.Node;
                else
                    return null;
            }
        }

        /// <summary>
        /// Access to hidden root node
        /// </summary>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public VirtualNode Root
        {
            get
            {
                return root;
            }
        }

        /// <summary>
        /// Count of root elements
        /// </summary>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public uint RootNodeCount
        {
            get
            {
                return root.ChildCount;
            }
            set
            {
                SetChildCount(root, value);
            }
        }

        /// <summary>
        /// User data attached to root node
        /// </summary>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public object RootNodeData
        {
            get
            {
                return root.Data;
            }
            set
            {
                root.Data = value;
            }
        }

        /// <summary>
        /// Currently selected node. If multiselect is enabled and there is more
        /// than 1 node selected, returns null. In such case use <see cref="SelectedCount"/>
        /// and <see cref="SelectedNodes"/> instead.
        /// </summary>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public VirtualNode SelectedNode
        {
            get
            {
                if (selectedNodes.Count > 0)
                    return selectedNodes[0];
                else
                    return null;
            }
            set
            {
                if (value == null)
                    ClearSelection();
                else
                {
                    value.CheckOwnerTree(this, "value");

                    SetSelection(value);
                }
            }
        }

        /// <summary>
        /// Count of selected nodes.
        /// </summary>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public int SelectedCount
        {
            get
            {
                return selectedNodes.Count;
            }
        }

        /// <summary>
        /// Access to selected nodes.
        /// </summary>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public ReadOnlyCollection<VirtualNode> SelectedNodes
        {
            get
            {
                return selectedNodesRO;
            }
        }

        /// <summary>
        /// List of checked nodes
        /// </summary>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public ReadOnlyCollection<VirtualNode> CheckedNodes
        {
            get
            {
                return checkedNodesRO;
            }
        }
        
        // TODO: Consider converting node proxies to node methods
        /// <summary>
        /// Access to node proxies
        /// </summary>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public NodeProxies Node
        {
            get
            {
                return node;
            }
        }

        /// <summary>
        /// Enables or disables user to expand or collapse nodes
        /// (they still can be expanded or collapsed from the code)
        /// </summary>
        public bool AllowExpandCollapse
        {
            get
            {
                return allowExpandCollapse;
            }
            set
            {
                allowExpandCollapse = value;
            }
        }

        /// <summary>
        /// Controls the visibility of control's external border.
        /// </summary>
        public bool ShowBorder
        {
            get
            {
                return showBorder;
            }
            set
            {
                showBorder = value;
                InvalidateMetrics();
            }
        }

        /// <summary>
        /// Allows or disallows to select multiple nodes at once.
        /// </summary>
        public bool MultiSelect
        {
            get
            {
                return multiSelect;
            }
            set
            {
                if (multiSelect != value)
                {
                    multiSelect = value;
                    if (!multiSelect)
                    {
                        ClearSelection();
                    }
                }
            }
        }

        /// <summary>
        /// Determines, whether tree lines shall be drawn.
        /// </summary>
        public bool ShowLines
        {
            get
            {
                return showLines;
            }
            set
            {
                showLines = value;

                InvalidateMetrics();
            }
        }

        /// <summary>
        /// Determines, whether root tree lines shall be drawn.
        /// </summary>
        public bool ShowRootLines
        {
            get
            {
                return showRootLines;
            }
            set
            {
                showRootLines = value;

                InvalidateMetrics();
            }
        }

        /// <summary>
        /// Determines, whether the expand/collapse glyphs shall
        /// be drawn.
        /// </summary>
        public bool ShowExpandMarks
        {
            get
            {
                return showExpandMarks;
            }
            set
            {
                showExpandMarks = value;

                InvalidateMetrics();
            }
        }

        /// <summary>
        /// Determines, whether to show checkboxes next to node contents
        /// </summary>
        public bool ShowCheckboxes
        {
            get
            {
                return showCheckboxes;
            }
            set
            {
                showCheckboxes = value;

                InvalidateMetrics();
            }
        }

        /// <summary>
        /// Determines, whether images shall be drawn.
        /// </summary>
        public bool ShowImages
        {
            get
            {
                return showImages;
            }
            set
            {
                showImages = value;

                InvalidateMetrics();
            }
        }

        /// <summary>
        /// Determines, whether to hottrack items
        /// </summary>
        public bool HotTrack
        {
            get
            {
                return hotTrack;
            }
            set
            {
                if (hotTrack != value)
                {
                    hotTrack = value;

                    switch (value)
                    {
                        case true:
                            {
                                Point p = PointToClient(Cursor.Position);
                                DoHandleHotTrack(p.X, p.Y);
                                Invalidate();

                                break;
                            }
                        case false:
                            {
                                DoClearHotTrack();
                                Invalidate();

                                break;
                            }
                    }
                }
            }
        }

        /// <summary>
        /// ImageList supplying images for tree nodes.
        /// </summary>
        public ImageList Images
        {
            get
            {
                return images;
            }
            set
            {
                if (images != value)
                {
                    if (images != null)
                        images.RecreateHandle -= images_RecreateHandle;

                    images = value;

                    if (images != null)
                        images.RecreateHandle += images_RecreateHandle;

                    Remeasure();
                    InvalidateMetrics();
                }
            }
        }

        /// <summary>
        /// Determines the way, how TreeView draws its nodes.
        /// </summary>
        public DrawModes DrawMode
        {
            get
            {
                return drawMode;
            }
            set
            {
                if (drawMode != value)
                {
                    renderer.Dispose();
                    renderer = null;

                    drawMode = value;

                    switch (drawMode)
                    {
                        case DrawModes.Default:
                            {
                                renderer = new DefaultNodeRenderer(this);
                                Remeasure();
                                
                                break;
                            }
                        case DrawModes.OwnerDrawItemContents:
                            {
                                renderer = new OwnerDrawNodeContentsRenderer(this);
                                Remeasure();
                                
                                break;
                            }
                        case DrawModes.OwnerDrawItems:
                            {
                                renderer = new OwnerDrawNodeRenderer(this);
                                Remeasure();
                                
                                break;
                            }
                        case DrawModes.System:
                            {
                                renderer = new SystemNodeRenderer(this);
                                ((SystemNodeRenderer)renderer).SetTheme(SystemNodeRenderer.SystemTheme.Explorer);
                                Remeasure();

                                break;
                            }
                        default:
                            throw new InvalidOperationException("Internal error: Unsupported draw mode!");
                    }
                }
            }
        }

        // Public events -----------------------------------------------------

        /// <summary>
        /// Called, when node will be made visible for the first time
        /// (usually when its parent is being expanded)
        /// </summary>
        public event InitNodeEventHandler InitNode;

        /// <summary>
        /// Called, when node is expanded for the first time to
        /// initialize its children.
        /// </summary>
        public event GetChildCountHandler GetChildCount;

        /// <summary>
        /// Called, when node is drawn to obtain its text to display.
        /// </summary>
        public event GetTextHandler GetText;

        /// <summary>
        /// Called, when node is drawn to obtain 
        /// </summary>
        public event GetImageIndexHandler GetImageIndex;

        public event NodeEventHandler NodeCollapsing;

        public event NodeEventHandler NodeCollapsed;

        public event NodeEventHandler NodeExpanding;

        public event NodeEventHandler NodeExpanded;

        public event NodeEventHandler NodeClicked;

        public event NodeEventHandler NodeDoubleClicked;

        public event GetNodeHeightEventHandler GetNodeHeight;

        public event GetNodeWidthEventHandler GetNodeWidth;

        public event GetNodeContentsWidthEventHandler GetNodeContentsWidth;

        public event DrawNodeContentsHandler DrawNodeContents;

        public event DrawNodeHandler DrawNode;

        public event GetNodeRegionsHandler GetNodeRegions;

        public event EventHandler SelectionChanged;

        public event GetNodeCheckboxVisibilityEventHandler GetNodeCheckboxVisibility;

        public event EventHandler FocusedNodeChanged;

        public event EventHandler CheckedNodesChanged;
    }
}